From c3a7c6c8063228280f810f0b39e98693cc9257be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Thu, 8 Oct 2026 08:10:30 +0200 Subject: [PATCH 1/2] SYNTH_VERILOG_SURGERY: edit copies of the sources before synthesis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surgery at home: SYNTH_VERILOG_SURGERY names a script that copies VERILOG_FILES and edits the copies before synthesis reads them, so the source tree stays as upstream has it. Two reasons to edit at synthesis time rather than in the source: - Bringing in upstream stays easy. A local edit to the source has to be carried across every bump of the upstream RTL and every regeneration of a generated core. The script applies it as a patch, as a procedure (find this construct, rewrite it), or both; a procedure can survive RTL changes that make a patch fail to apply. - Some seams are PDK-specific. Whether a register file becomes a module of its own that a generated register file replaces, or stays flops for synthesis to map, depends on which gives the best QoR on that PDK, not on the source code. The same RTL can want the seam on one platform and not another, so it belongs in the platform's flow configuration. A hack, documented as one: nothing checks what it changed. Good for simple RTL, especially generated RTL (firtool, sv2v), whose text is regular enough to edit reliably. Unsuitable for idiomatic SystemVerilog code bases with include files, macros, packages, `bind` and other advanced language features, where an edit to the text cannot see what the code means. It runs where the RTL is read (synth_verilog_files in util.tcl, called by read_design_sources and by the SYNTH_USE_SYN frontend), with SYNTH_VERILOG_SURGERY_ARGS, then `--out-dir $(OBJECTS_DIR)/surgery --` and the VERILOG_FILES (by PYTHON_EXE when it ends in .py). It writes one copy per source under the same file name, and synthesis reads the copies. They are kept for inspection but are no make target and no build output: the Makefile and variables.mk are unchanged. A failing script, or a copy it did not write, stops synthesis. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Øyvind Harboe --- docs/user/FlowVariables.md | 4 ++++ flow/scripts/synth_preamble.tcl | 6 +++--- flow/scripts/synth_syn.tcl | 5 +++-- flow/scripts/util.tcl | 35 +++++++++++++++++++++++++++++++++ flow/scripts/variables.json | 12 +++++++++++ flow/scripts/variables.yaml | 32 ++++++++++++++++++++++++++++++ 6 files changed, 89 insertions(+), 5 deletions(-) diff --git a/docs/user/FlowVariables.md b/docs/user/FlowVariables.md index c575653135..5056510bbe 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| Surgery at home: a script that copies VERILOG_FILES and edits the copies before synthesis reads them, so the source tree stays as upstream has it. Two uses. First, local edits are applied at synthesis time as a patch, as a procedure, or both, instead of being carried in the source, so bringing in a new upstream or a regenerated core stays a plain bump; a procedure can survive RTL changes that make a patch fail to apply. Second, PDK-specific seams: whether a register file, say, becomes a module of its own that a generated netlist replaces depends on which gives the best QoR on that PDK, not on the source code, so the seam is made in the platform's flow configuration. A hack; you asked for it, you deserve the results. Good for simple RTL, especially generated RTL (firtool, sv2v), whose text is regular enough to edit reliably. Unsuitable for idiomatic SystemVerilog code bases with include files, macros, packages, `bind` and other advanced language features, where an edit to the text cannot see what the code means. 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. They are kept for inspection but are no make target or build output. Input file names must be unique. Nothing checks what the script changed: write it to fail loudly when it cannot recognise its input.| | +| 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..a2386dea51 100644 --- a/flow/scripts/util.tcl +++ b/flow/scripts/util.tcl @@ -336,3 +336,38 @@ 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 (a hack), 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 for inspection. 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..980e0044ae 100644 --- a/flow/scripts/variables.json +++ b/flow/scripts/variables.json @@ -1407,6 +1407,18 @@ "synth" ] }, + "SYNTH_VERILOG_SURGERY": { + "description": "Surgery at home: a script that copies VERILOG_FILES and edits the copies before synthesis reads them, so the source tree stays as upstream has it. Two uses. First, local edits are applied at synthesis time as a patch, as a procedure, or both, instead of being carried in the source, so bringing in a new upstream or a regenerated core stays a plain bump; a procedure can survive RTL changes that make a patch fail to apply. Second, PDK-specific seams: whether a register file, say, becomes a module of its own that a generated netlist replaces depends on which gives the best QoR on that PDK, not on the source code, so the seam is made in the platform's flow configuration. A hack; you asked for it, you deserve the results. Good for simple RTL, especially generated RTL (firtool, sv2v), whose text is regular enough to edit reliably. Unsuitable for idiomatic SystemVerilog code bases with include files, macros, packages, `bind` and other advanced language features, where an edit to the text cannot see what the code means. 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. They are kept for inspection but are no make target or build output. Input file names must be unique. Nothing checks what the script changed: write it to fail loudly when it cannot recognise its input.\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..e93b4c659c 100644 --- a/flow/scripts/variables.yaml +++ b/flow/scripts/variables.yaml @@ -211,6 +211,38 @@ ADDITIONAL_MEMORIES: docs/user/AutoMemories.md. stages: - synth +SYNTH_VERILOG_SURGERY: + description: > + Surgery at home: a script that copies VERILOG_FILES and edits the + copies before synthesis reads them, so the source tree stays as + upstream has it. Two uses. First, local edits are applied at synthesis + time as a patch, as a procedure, or both, instead of being carried in + the source, so bringing in a new upstream or a regenerated core stays + a plain bump; a procedure can survive RTL changes that make a patch + fail to apply. Second, PDK-specific seams: whether a register file, + say, becomes a module of its own that a generated netlist replaces + depends on which gives the best QoR on that PDK, not on the source + code, so the seam is made in the platform's flow configuration. A + hack; you asked for it, you deserve the results. Good for simple RTL, + especially generated RTL (firtool, sv2v), whose text is regular enough + to edit reliably. Unsuitable for idiomatic SystemVerilog code bases + with include files, macros, packages, `bind` and other advanced + language features, where an edit to the text cannot see what the code + means. 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. They are kept for + inspection but are no make target or build output. Input file names + must be unique. Nothing checks what the script changed: write it to + fail loudly when it cannot recognise its input. + 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. From 250b2f537b196775eef5a8225ac07ae36e76b034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Fri, 9 Oct 2026 08:38:50 +0200 Subject: [PATCH 2/2] SYNTH_VERILOG_SURGERY: describe it as RTL surgery on the books MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The variable's documentation and the util.tcl comment now say what the step is for: 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. A diff of a copy against its original shows what the surgery changed. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Øyvind Harboe --- docs/user/FlowVariables.md | 2 +- flow/scripts/util.tcl | 9 ++++---- flow/scripts/variables.json | 2 +- flow/scripts/variables.yaml | 41 +++++++++++++++++-------------------- 4 files changed, 26 insertions(+), 28 deletions(-) diff --git a/docs/user/FlowVariables.md b/docs/user/FlowVariables.md index 5056510bbe..fcf1d6fab8 100644 --- a/docs/user/FlowVariables.md +++ b/docs/user/FlowVariables.md @@ -313,7 +313,7 @@ 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| Surgery at home: a script that copies VERILOG_FILES and edits the copies before synthesis reads them, so the source tree stays as upstream has it. Two uses. First, local edits are applied at synthesis time as a patch, as a procedure, or both, instead of being carried in the source, so bringing in a new upstream or a regenerated core stays a plain bump; a procedure can survive RTL changes that make a patch fail to apply. Second, PDK-specific seams: whether a register file, say, becomes a module of its own that a generated netlist replaces depends on which gives the best QoR on that PDK, not on the source code, so the seam is made in the platform's flow configuration. A hack; you asked for it, you deserve the results. Good for simple RTL, especially generated RTL (firtool, sv2v), whose text is regular enough to edit reliably. Unsuitable for idiomatic SystemVerilog code bases with include files, macros, packages, `bind` and other advanced language features, where an edit to the text cannot see what the code means. 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. They are kept for inspection but are no make target or build output. Input file names must be unique. Nothing checks what the script changed: write it to fail loudly when it cannot recognise its input.| | +| 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.| | diff --git a/flow/scripts/util.tcl b/flow/scripts/util.tcl index a2386dea51..0fa432acf4 100644 --- a/flow/scripts/util.tcl +++ b/flow/scripts/util.tcl @@ -338,10 +338,11 @@ proc source_step_tcl { hook_type step_name } { } # The Verilog files synthesis reads: VERILOG_FILES or, with -# SYNTH_VERILOG_SURGERY (a hack), 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 for inspection. They are no make -# target and no build output. +# 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) diff --git a/flow/scripts/variables.json b/flow/scripts/variables.json index 980e0044ae..af0d60e8c9 100644 --- a/flow/scripts/variables.json +++ b/flow/scripts/variables.json @@ -1408,7 +1408,7 @@ ] }, "SYNTH_VERILOG_SURGERY": { - "description": "Surgery at home: a script that copies VERILOG_FILES and edits the copies before synthesis reads them, so the source tree stays as upstream has it. Two uses. First, local edits are applied at synthesis time as a patch, as a procedure, or both, instead of being carried in the source, so bringing in a new upstream or a regenerated core stays a plain bump; a procedure can survive RTL changes that make a patch fail to apply. Second, PDK-specific seams: whether a register file, say, becomes a module of its own that a generated netlist replaces depends on which gives the best QoR on that PDK, not on the source code, so the seam is made in the platform's flow configuration. A hack; you asked for it, you deserve the results. Good for simple RTL, especially generated RTL (firtool, sv2v), whose text is regular enough to edit reliably. Unsuitable for idiomatic SystemVerilog code bases with include files, macros, packages, `bind` and other advanced language features, where an edit to the text cannot see what the code means. 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. They are kept for inspection but are no make target or build output. Input file names must be unique. Nothing checks what the script changed: write it to fail loudly when it cannot recognise its input.\n", + "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" ] diff --git a/flow/scripts/variables.yaml b/flow/scripts/variables.yaml index e93b4c659c..75f8c4ca42 100644 --- a/flow/scripts/variables.yaml +++ b/flow/scripts/variables.yaml @@ -213,29 +213,26 @@ ADDITIONAL_MEMORIES: - synth SYNTH_VERILOG_SURGERY: description: > - Surgery at home: a script that copies VERILOG_FILES and edits the - copies before synthesis reads them, so the source tree stays as - upstream has it. Two uses. First, local edits are applied at synthesis - time as a patch, as a procedure, or both, instead of being carried in - the source, so bringing in a new upstream or a regenerated core stays - a plain bump; a procedure can survive RTL changes that make a patch - fail to apply. Second, PDK-specific seams: whether a register file, - say, becomes a module of its own that a generated netlist replaces - depends on which gives the best QoR on that PDK, not on the source - code, so the seam is made in the platform's flow configuration. A - hack; you asked for it, you deserve the results. Good for simple RTL, - especially generated RTL (firtool, sv2v), whose text is regular enough - to edit reliably. Unsuitable for idiomatic SystemVerilog code bases - with include files, macros, packages, `bind` and other advanced - language features, where an edit to the text cannot see what the code - means. 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 + 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. They are kept for - inspection but are no make target or build output. Input file names - must be unique. Nothing checks what the script changed: write it to - fail loudly when it cannot recognise its input. + 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: