Good macro configs from 3cpu-512w-nobuff-wrapped.

From commit 4e60cd8416751ad0bf38814006a930c58a6f0468
diff --git a/openlane/user_proj_example/config.tcl b/openlane/user_proj_example/config.tcl
index 4e5cc61..a50398d 100644
--- a/openlane/user_proj_example/config.tcl
+++ b/openlane/user_proj_example/config.tcl
@@ -1,41 +1,57 @@
-# SPDX-FileCopyrightText: 2020 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.
-# SPDX-License-Identifier: Apache-2.0
-
 set script_dir [file dirname [file normalize [info script]]]
 
 set ::env(DESIGN_NAME) user_proj_example
 
 set ::env(VERILOG_FILES) "\
 	$script_dir/../../verilog/rtl/defines.v \
-	$script_dir/../../verilog/rtl/user_proj_example.v"
+	$script_dir/../../verilog/rtl/user_proj_example.v \
+	$script_dir/../../verilog/rtl/softshell/rtl/softshell_top.v \
+	$script_dir/../../verilog/rtl/softshell/rtl/rv_core.v \
+	$script_dir/../../verilog/rtl/softshell/rtl/pinmux.v \
+	$script_dir/../../verilog/rtl/softshell/rtl/pcpi_flexio.v \
+	$script_dir/../../verilog/rtl/softshell/third_party/verilog-wishbone/rtl/wb_arbiter_3.v \
+	$script_dir/../../verilog/rtl/softshell/third_party/verilog-wishbone/rtl/wb_arbiter_4.v \
+	$script_dir/../../verilog/rtl/softshell/third_party/verilog-wishbone/rtl/wb_arbiter_5.v \
+	$script_dir/../../verilog/rtl/softshell/third_party/verilog-wishbone/rtl/arbiter.v \
+	$script_dir/../../verilog/rtl/softshell/third_party/verilog-wishbone/rtl/priority_encoder.v \
+	$script_dir/../../verilog/rtl/softshell/third_party/verilog-wishbone/rtl/wb_mux_3.v \
+	$script_dir/../../verilog/rtl/softshell/third_party/verilog-wishbone/rtl/wb_mux_5.v \
+	$script_dir/../../verilog/rtl/softshell/third_party/picorv32_wb/mem_ff_wb.v \
+	$script_dir/../../verilog/rtl/softshell/third_party/picorv32_wb/simpleuart.v \
+	$script_dir/../../verilog/rtl/softshell/third_party/picorv32_wb/spimemio.v \
+	$script_dir/../../verilog/rtl/softshell/third_party/picorv32_wb/gpio32_wb.v \
+	$script_dir/../../verilog/rtl/softshell/third_party/picorv32_wb/picorv32.v \
+	$script_dir/../../verilog/rtl/softshell/third_party/wb2axip/rtl/afifo.v"
 
-set ::env(CLOCK_PORT) ""
-set ::env(CLOCK_NET) "counter.clk"
-set ::env(CLOCK_PERIOD) "10"
+#set ::env(VERILOG_INCLUDE_DIRS) "\
+#	$script_dir/../../softshell"
+
+# For the manually instantiated buffers in softshell_top.
+# set ::env(SYNTH_READ_BLACKBOX_LIB) 1
+
+set ::env(CLOCK_PORT) "wb_clk_i"
+#set ::env(CLOCK_NET) "softshell.wb_clk_i"
+set ::env(CLOCK_PERIOD) "20"
 
 set ::env(FP_SIZING) absolute
-set ::env(DIE_AREA) "0 0 600 600"
+set ::env(DIE_AREA) "0 0 2500 3100"
 set ::env(DESIGN_IS_CORE) 0
+set ::env(FP_PDN_CORE_RING) 0
 
-set ::env(VDD_NETS) [list {vccd1} {vccd2} {vdda1} {vdda2}]
-set ::env(GND_NETS) [list {vssd1} {vssd2} {vssa1} {vssa2}]
+#set ::env(GLB_RT_ALLOW_CONGESTION) 1
+set ::env(GLB_RT_MAXLAYER) 5
+
+# 0.4 results in shorts, 0.35 - 0.3 sometimes results in metal loops due to routing.
+set ::env(PL_TARGET_DENSITY) 0.38
+
+# Don't use met5.
+set ::env(GLB_RT_OBS) "met5 0 0 2500 3100"
+
+# Diodes inserted using interactive.tcl.
+set ::env(DIODE_INSERTION_STRATEGY) 0
+
+set ::env(ROUTING_CORES) 6
 
 set ::env(FP_PIN_ORDER_CFG) $script_dir/pin_order.cfg
-
-set ::env(PL_BASIC_PLACEMENT) 1
-set ::env(PL_TARGET_DENSITY) 0.15
-
-# If you're going to use multiple power domains, then keep this disabled.
-set ::env(RUN_CVC) 0
+# set ::env(FP_CONTEXT_DEF) $script_dir/../user_project_wrapper/runs/user_project_wrapper/tmp/floorplan/ioPlacer.def.macro_placement.def
+# set ::env(FP_CONTEXT_LEF) $script_dir/../user_project_wrapper/runs/user_project_wrapper/tmp/merged_unpadded.lef
diff --git a/openlane/user_proj_example/copy_results.sh b/openlane/user_proj_example/copy_results.sh
new file mode 100755
index 0000000..d31feab
--- /dev/null
+++ b/openlane/user_proj_example/copy_results.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+set -x
+
+IN_PATH="./$1"
+OUT_PATH="../.."
+ARTIFACT="user_proj_example"
+
+echo "Copying results from '${IN_PATH}' to '${OUT_PATH}'"
+
+cp -pf "${IN_PATH}/results/routing/${ARTIFACT}.def" "${OUT_PATH}/def/${ARTIFACT}.def"
+cp -pf "${IN_PATH}/results/magic/${ARTIFACT}.gds" "${OUT_PATH}/gds/${ARTIFACT}.gds"
+cp -pf "${IN_PATH}/results/magic/${ARTIFACT}.lef" "${OUT_PATH}/lef/${ARTIFACT}.lef"
+cp -pf "${IN_PATH}/results/magic/${ARTIFACT}.mag" "${OUT_PATH}/mag/${ARTIFACT}.mag"
+cp -pf "${IN_PATH}/results/lvs/${ARTIFACT}.lvs.powered.v" "${OUT_PATH}/verilog/gl/${ARTIFACT}.v"
+cp -pf "${IN_PATH}/results/magic/${ARTIFACT}.spice" "${OUT_PATH}/spi/lvs/${ARTIFACT}.spice"
+
+echo "Copying summary"
+cp -pf "${IN_PATH}/reports/final_summary_report.csv" "${OUT_PATH}/openlane/${ARTIFACT}/"
+
+# echo "Removing old results folder and copying all results..."
+# rm -rf "${OUT_PATH}/runs/${ARTIFACT}/*"
+# cp -prf "${IN_PATH}/*" "${OUT_PATH}/openlane/${ARTIFACT}/runs/${ARTIFACT}"
+
+echo "Done"
diff --git a/openlane/user_proj_example/interactive.tcl b/openlane/user_proj_example/interactive.tcl
new file mode 100644
index 0000000..d8888ae
--- /dev/null
+++ b/openlane/user_proj_example/interactive.tcl
@@ -0,0 +1,104 @@
+#!/usr/bin/tclsh
+# Copyright 2020 Efabless Corporation
+# Copyright 2020 Sylvain Munaut
+#
+# 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.
+
+
+package require openlane;
+
+proc insert_diode {args} {
+	puts_info "Insert diodes..."
+
+	# Custom insertion script
+	set ::env(SAVE_DEF) $::env(TMP_DIR)/placement/$::env(DESIGN_NAME).diodes.def
+	try_catch python3 $::env(DESIGN_DIR)/scripts/place_diodes.py -l $::env(MERGED_LEF) -id $::env(CURRENT_DEF) -o $::env(SAVE_DEF) |& tee $::env(TERMINAL_OUTPUT) $::env(LOG_DIR)/diodes.log
+	set_def $::env(SAVE_DEF)
+
+	# Legalize
+	detailed_placement
+}
+
+proc run_flow {args} {
+	puts_info "Starting custom interactive flow..."
+
+	set script_dir [file dirname [file normalize [info script]]]
+	set options {
+		{-save_path optional}
+		{-tag optional}
+	}
+	set flags {-save}
+	parse_key_args "run_flow" args arg_values $options flags_map $flags -no_consume
+
+	prep -design $script_dir {*}$args
+
+	run_synthesis
+	run_floorplan
+	run_placement
+	run_cts
+	insert_diode
+	#gen_pdn
+	run_routing
+
+	if { $::env(DIODE_INSERTION_STRATEGY) == 2 } {
+		run_antenna_check
+		heal_antenna_violators; # modifies the routed DEF
+	}
+
+	if { $::env(LVS_INSERT_POWER_PINS) } {
+		write_powered_verilog
+		set_netlist $::env(lvs_result_file_tag).powered.v
+	}
+
+	run_magic
+
+	run_magic_spice_export
+
+	if {  [info exists flags_map(-save) ] } {
+		if { [info exists arg_values(-save_path)] } {
+			save_views 	-lef_path $::env(magic_result_file_tag).lef \
+				-def_path $::env(tritonRoute_result_file_tag).def \
+				-gds_path $::env(magic_result_file_tag).gds \
+				-mag_path $::env(magic_result_file_tag).mag \
+				-spice_path $::env(magic_result_file_tag).spice \
+				-verilog_path $::env(CURRENT_NETLIST) \
+				-save_path $arg_values(-save_path) \
+				-tag $::env(RUN_TAG)
+		} else  {
+			save_views 	-lef_path $::env(magic_result_file_tag).lef \
+				-def_path $::env(tritonRoute_result_file_tag).def \
+				-mag_path $::env(magic_result_file_tag).mag \
+				-gds_path $::env(magic_result_file_tag).gds \
+				-spice_path $::env(magic_result_file_tag).spice \
+				-verilog_path $::env(CURRENT_NETLIST) \
+				-tag $::env(RUN_TAG)
+		}
+	}
+
+	# Physical verification
+
+	run_magic_drc
+
+	run_lvs; # requires run_magic_spice_export
+
+	run_antenna_check
+
+	generate_final_summary_report
+
+	# Also run magic antenna check to compare (default uses OR antenna check).
+	run_magic_antenna_check
+
+	puts_success "Flow Completed Without Fatal Errors."
+}
+
+run_flow {*}$argv
diff --git a/openlane/user_proj_example/scripts/place_diodes.py b/openlane/user_proj_example/scripts/place_diodes.py
new file mode 100644
index 0000000..1e810ca
--- /dev/null
+++ b/openlane/user_proj_example/scripts/place_diodes.py
@@ -0,0 +1,341 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020  Sylvain Munaut <tnt@246tNt.com>
+# SPDX-License-Identifier: Apache-2.0
+#
+
+import argparse
+import random
+import sys
+
+import opendbpy as odb
+
+
+class DiodeInserter:
+
+	def __init__(self, block, diode_cell, diode_pin, fake_diode_cell=None, side_strategy='source', short_span=0, port_protect=[], verbose=False):
+		self.block = block
+		self.verbose = verbose
+
+		self.diode_cell = diode_cell
+		self.diode_pin = diode_pin
+		self.fake_diode_cell = fake_diode_cell
+		self.side_strategy = side_strategy
+		self.short_span = short_span
+		self.port_protect = port_protect
+
+		self.true_diode_master = block.getDataBase().findMaster(diode_cell)
+		self.fake_diode_master = block.getDataBase().findMaster(fake_diode_cell) if (fake_diode_cell is not None) else None
+		if not self.check():
+			raise RuntimeError('True and Fake diodes are inconsistent')
+
+		self.diode_master = self.fake_diode_master or self.true_diode_master
+		self.diode_site   = self.true_diode_master.getSite().getConstName()
+
+		self.inserted = {}
+
+	def check(self):
+		if self.fake_diode_master is None:
+			return True
+
+		tm = self.true_diode_master
+		fm = self.fake_diode_master
+
+		if fm.getSite() is None:
+			self.error("[!] Fake diode cell missing SITE attribute");x
+		else:
+			if fm.getSite().getConstName() != tm.getSite().getConstName():
+				return False
+
+		if fm.getWidth() != tm.getWidth():
+			return False
+		if fm.getHeight() != tm.getHeight():
+			return False
+
+		return True
+
+	def debug(self, msg):
+		if self.verbose:
+			print(msg, file=sys.stderr)
+
+	def error(self, msg):
+		print(msg, file=sys.stderr)
+
+	def net_source(self, net):
+		# See if it's an input pad
+		for bt in net.getBTerms():
+			if bt.getIoType() != 'INPUT':
+				continue
+			good, x, y = bt.getFirstPinLocation()
+			if good:
+				return (x, y)
+
+		# Or maybe output of a cell
+		x = odb.new_int(0)
+		y = odb.new_int(0)
+
+		for it in net.getITerms():
+			if not it.isOutputSignal():
+				continue
+			if it.getAvgXY(x,y):
+				return ( odb.get_int(x), odb.get_int(y) )
+
+		# Nothing found
+		return None
+
+	def net_from_pin(self, net, io_types=None):
+		for bt in net.getBTerms():
+			if (io_types is None) or (bt.getIoType() in io_types):
+				return True
+		return False
+
+	def net_has_diode(self, net):
+		for it in net.getITerms():
+			cell_type = it.getInst().getMaster().getConstName()
+			cell_pin  = it.getMTerm().getConstName()
+			if (cell_type == self.diode_cell) and (cell_pin == self.diode_pin):
+				return True
+		else:
+			return False
+
+	def net_span(self, net):
+		xs = []
+		ys = []
+
+		for bt in net.getBTerms():
+			good, x, y = bt.getFirstPinLocation()
+			if good:
+				xs.append(x)
+				ys.append(y)
+
+		for it in net.getITerms():
+			x, y = self.pin_position(it)
+			xs.append(x)
+			ys.append(y)
+
+		if len(xs) == 0:
+			return 0
+
+		return (max(ys) - min(ys)) + (max(xs) - min(xs))
+
+	def pin_position(self, it):
+		px = odb.new_int(0)
+		py = odb.new_int(0)
+
+		if it.getAvgXY(px,py):
+			# Got it
+			return odb.get_int(px), odb.get_int(py)
+		else:
+			# Failed, use the center coordinate of the instance as fall back
+			return it.getInst().getLocation()
+
+	def place_diode_stdcell(self, it, px, py, src_pos=None):
+		# Get information about the instance
+		inst_name  = it.getInst().getConstName()
+		inst_width = it.getInst().getMaster().getWidth()
+		inst_pos   = it.getInst().getLocation()
+		inst_ori   = it.getInst().getOrient()
+
+		# Is the pin left-ish, center-ish or right-ish ?
+		pos = None
+
+		if self.side_strategy == 'source':
+			# Always be on the side of the source
+			if src_pos is not None:
+				pos = 'l' if (src_pos[0] < inst_pos[0]) else 'r'
+
+		elif self.side_strategy == 'pin':
+			# Always be on the side of the pin
+			pos = 'l' if (px < (inst_pos[0] + inst_width // 2)) else 'r'
+
+		elif self.side_strategy == 'balanced':
+			# If pin is really on the side, use that, else use source side
+			th_left  = int(inst_pos[0] + inst_width * 0.25)
+			th_right = int(inst_pos[0] + inst_width * 0.75)
+
+			if px < th_left:
+				pos = 'l'
+			elif px > th_right:
+				pos = 'r'
+			elif src_pos is not None:
+				# Sort of middle, so put it on the side where signal is coming from
+				pos = 'l' if (src_pos[0] < inst_pos[0]) else 'r'
+
+		if pos is None:
+			# Coin toss ...
+			pos = 'l' if (random.random() > 0.5) else 'r'
+
+		# X position
+		dw = self.diode_master.getWidth()
+
+		if pos == 'l':
+			dx = inst_pos[0] - dw * (1 + self.inserted.get((inst_name, 'l'), 0))
+		else:
+			dx = inst_pos[0] + inst_width + dw * self.inserted.get((inst_name, 'r'), 0)
+
+		# Record insertion
+		self.inserted[(inst_name, pos)] = self.inserted.get((inst_name, pos), 0) + 1
+
+		# Done
+		return dx, inst_pos[1], inst_ori
+
+	def place_diode_macro(self, it, px, py, src_pos=None):
+		# Scan all rows to see how close we can get to the point
+		best = None
+
+		for row in self.block.getRows():
+			rbb = row.getBBox()
+
+			dx = max(min(rbb.xMax(), px), rbb.xMin())
+			dy = rbb.yMin()
+			do = row.getOrient()
+
+			d = abs(px - dx) + abs(py - dy)
+
+			if (best is None) or (best[0] > d):
+				best = (d, dx, dy, do)
+
+		return best[1:]
+
+	def insert_diode(self, it, src_pos, force_true=False):
+		# Get information about the instance
+		inst       = it.getInst()
+		inst_cell  = inst.getMaster().getConstName()
+		inst_name  = inst.getConstName()
+		inst_pos   = inst.getLocation()
+		inst_site  = inst.getMaster().getSite().getConstName() if (inst.getMaster().getSite() is not None) else None
+
+		# Find where the pin is
+		px, py = self.pin_position(it)
+
+		# Apply standard cell or macro placement ?
+		if inst_site == self.diode_site:
+			dx, dy, do = self.place_diode_stdcell(it, px, py, src_pos)
+		else:
+			dx, dy, do = self.place_diode_macro(it, px, py, src_pos)
+
+		# Insert instance and wire it up
+		diode_inst_name = 'ANTENNA_' + inst_name + '_' + it.getMTerm().getConstName()
+		diode_master = self.true_diode_master if force_true else self.diode_master
+
+		diode_inst = odb.dbInst_create(self.block, diode_master, diode_inst_name)
+
+		diode_inst.setOrient(do)
+		diode_inst.setLocation(dx, dy)
+		diode_inst.setPlacementStatus('PLACED')
+
+		ait = diode_inst.findITerm(self.diode_pin)
+		odb.dbITerm_connect(ait, it.getNet())
+
+	def execute(self):
+		# Scan all nets
+		for net in self.block.getNets():
+			# Skip special nets
+			if net.isSpecial():
+				self.debug(f"[d] Skipping special net {net.getConstName():s}")
+				continue
+
+			# Check if we already have diode on the net
+			# if yes, then we assume that the user took care of that net manually
+			if self.net_has_diode(net):
+				self.debug(f"[d] Skipping manually protected net {net.getConstName():s}")
+				continue
+
+			# Find signal source (first one found ...)
+			src_pos = self.net_source(net)
+
+			# Is this an IO we need to protect
+			io_protect = self.net_from_pin(net, io_types=self.port_protect)
+			if io_protect:
+				self.debug(f"[d] Forcing protection diode on net  {net.getConstName():s}")
+
+			# Determine the span of the signal and skip small internal nets
+			span = self.net_span(net)
+			if (span < self.short_span) and not io_protect:
+				self.debug(f"[d] Skipping small net {net.getConstName():s} ({span:d})")
+				continue
+
+			# Scan all internal terminals
+			for it in net.getITerms():
+				if it.isInputSignal():
+					self.insert_diode(it, src_pos, force_true=io_protect)
+
+
+# Arguments
+parser = argparse.ArgumentParser(
+		description='Diode Insertion script')
+
+parser.add_argument('--lef', '-l',
+		nargs='+',
+		type=str,
+		default=None,
+		required=True,
+		help='Input LEF file(s)')
+
+parser.add_argument('--input-def', '-id', required=True,
+		help='DEF view of the design that needs to have diodes inserted')
+
+parser.add_argument('--output-def', '-o', required=True,
+		help='Output DEF file')
+
+parser.add_argument('--verbose', '-v', action="store_true", default=False,
+		help='Enable verbose debug output')
+
+parser.add_argument('--diode-cell', '-c', default='sky130_fd_sc_hd__diode_2',
+		help='Name of the cell to use as diode')
+
+parser.add_argument('--fake-diode-cell', '-f',
+		help='Name of the cell to use as fake diode')
+
+parser.add_argument('--diode-pin', '-p', default='DIODE',
+		help='Name of the pin to use on diode cells')
+
+parser.add_argument('--side-strategy', choices=['source', 'pin', 'balanced', 'random'], default='source',
+		help='Strategy to select if placing diode left/right of the cell')
+
+parser.add_argument('--short-span', '-s', default=90000, type=int,
+		help='Maximum span of a net to be considered "short" and not needing a diode')
+
+parser.add_argument('--port-protect', choices=['none', 'in', 'out', 'both'], default='in',
+		help='Always place a true diode on nets connected to selected ports')
+
+
+
+args = parser.parse_args()
+input_lef_file_names = args.lef
+input_def_file_name = args.input_def
+output_def_file_name = args.output_def
+
+# Load
+db_design = odb.dbDatabase.create()
+
+for lef in input_lef_file_names:
+    odb.read_lef(db_design, lef)
+odb.read_def(db_design, input_def_file_name)
+
+chip_design = db_design.getChip()
+block_design = chip_design.getBlock()
+top_design_name = block_design.getConstName()
+print("Design name:", top_design_name)
+
+
+pp_val = {
+	'none': [],
+	'in': ['INPUT'],
+	'out': ['OUTPUT'],
+	'both': ['INPUT', 'OUTPUT'],
+}
+
+di = DiodeInserter(block_design,
+	diode_cell = args.diode_cell,
+	diode_pin = args.diode_pin,
+	fake_diode_cell = args.fake_diode_cell,
+	side_strategy = args.side_strategy,
+	short_span = args.short_span,
+	port_protect = pp_val[args.port_protect],
+	verbose = args.verbose
+)
+di.execute()
+
+# Write result
+odb.write_def(block_design, output_def_file_name)
diff --git a/openlane/user_project_wrapper/config.tcl b/openlane/user_project_wrapper/config.tcl
index e60639f..dcf50f3 100644
--- a/openlane/user_project_wrapper/config.tcl
+++ b/openlane/user_project_wrapper/config.tcl
@@ -1,43 +1,49 @@
-# SPDX-FileCopyrightText: 2020 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.
-# SPDX-License-Identifier: Apache-2.0
-
-# Base Configurations. Don't Touch
-# section begin
 set script_dir [file dirname [file normalize [info script]]]
+
 set ::env(DESIGN_NAME) user_project_wrapper
-#section end
+set ::env(FP_PIN_ORDER_CFG) $script_dir/pin_order.cfg
 
+set ::env(PDN_CFG) $script_dir/pdn.tcl
+set ::env(FP_PDN_CORE_RING) 1
+set ::env(FP_SIZING) absolute
+set ::env(DIE_AREA) "0 0 2920 3520"
 
-# User Configurations
+set ::unit 2.4
+set ::env(FP_IO_VEXTEND) [expr 2*$::unit]
+set ::env(FP_IO_HEXTEND) [expr 2*$::unit]
+set ::env(FP_IO_VLENGTH) $::unit
+set ::env(FP_IO_HLENGTH) $::unit
 
-## Source Verilog Files
+set ::env(FP_IO_VTHICKNESS_MULT) 4
+set ::env(FP_IO_HTHICKNESS_MULT) 4
+
+set ::env(CLOCK_PORT) "wb_clk_i"
+#set ::env(CLOCK_NET) "mprj.clk"
+
+set ::env(CLOCK_PERIOD) "20"
+
+set ::env(PL_OPENPHYSYN_OPTIMIZATIONS) 0
+set ::env(DIODE_INSERTION_STRATEGY) 0
+
+set ::env(ROUTING_CORES) 6
+
+# Don't use li1 (high resistance), met4 (macro power), or met5 (power).
+set ::env(GLB_RT_MINLAYER) 2
+set ::env(GLB_RT_MAXLAYER) 4
+set ::env(GLB_RT_OBS) "li1 0 0 2920 3520, met4 0 0 2920 3520, met5 0 0 2920 3520"
+
+# Try really hard to route.
+set ::env(ROUTING_OPT_ITERS) 200
+
+# Need to fix a FastRoute bug for this to work, but it's good
+# for a sense of "isolation"
+set ::env(MAGIC_ZEROIZE_ORIGIN) 0
+set ::env(MAGIC_WRITE_FULL_LEF) 0
+
 set ::env(VERILOG_FILES) "\
 	$script_dir/../../verilog/rtl/defines.v \
 	$script_dir/../../verilog/rtl/user_project_wrapper.v"
 
-## Clock configurations
-set ::env(CLOCK_PORT) "user_clock2"
-set ::env(CLOCK_NET) "mprj.clk"
-
-set ::env(CLOCK_PERIOD) "10"
-
-## Internal Macros
-### Macro Placement
-set ::env(MACRO_PLACEMENT_CFG) $script_dir/macro.cfg
-
-### Black-box verilog and views
 set ::env(VERILOG_FILES_BLACKBOX) "\
 	$script_dir/../../verilog/rtl/defines.v \
 	$script_dir/../../verilog/rtl/user_proj_example.v"
@@ -47,60 +53,3 @@
 
 set ::env(EXTRA_GDS_FILES) "\
 	$script_dir/../../gds/user_proj_example.gds"
-
-
-# The following is because there are no std cells in the example wrapper project.
-set ::env(SYNTH_TOP_LEVEL) 1
-set ::env(PL_RANDOM_GLB_PLACEMENT) 1
-set ::env(PL_OPENPHYSYN_OPTIMIZATIONS) 0
-set ::env(DIODE_INSERTION_STRATEGY) 0
-set ::env(FILL_INSERTION) 0
-set ::env(TAP_DECAP_INSERTION) 0
-set ::env(CLOCK_TREE_SYNTH) 0
-
-
-# DON'T TOUCH THE FOLLOWING SECTIONS
-
-# This makes sure that the core rings are outside the boundaries
-# of your block.
-set ::env(MAGIC_ZEROIZE_ORIGIN) 0
-
-# Area Configurations. DON'T TOUCH.
-set ::env(FP_SIZING) absolute
-set ::env(DIE_AREA) "0 0 2920 3520"
-
-# Power & Pin Configurations. DON'T TOUCH.
-set ::env(FP_PDN_CORE_RING) 1
-set ::env(FP_PDN_CORE_RING_VWIDTH) 3
-set ::env(FP_PDN_CORE_RING_HWIDTH) $::env(FP_PDN_CORE_RING_VWIDTH)
-set ::env(FP_PDN_CORE_RING_VOFFSET) 14
-set ::env(FP_PDN_CORE_RING_HOFFSET) $::env(FP_PDN_CORE_RING_VOFFSET)
-set ::env(FP_PDN_CORE_RING_VSPACING) 1.7
-set ::env(FP_PDN_CORE_RING_HSPACING) $::env(FP_PDN_CORE_RING_VSPACING)
-
-set ::env(FP_PDN_VWIDTH) 3
-set ::env(FP_PDN_HWIDTH) 3
-set ::env(FP_PDN_VOFFSET) 0
-set ::env(FP_PDN_HOFFSET) $::env(FP_PDN_VOFFSET)
-set ::env(FP_PDN_VPITCH) 180
-set ::env(FP_PDN_HPITCH) $::env(FP_PDN_VPITCH)
-set ::env(FP_PDN_VSPACING) [expr 5*$::env(FP_PDN_CORE_RING_VWIDTH)]
-set ::env(FP_PDN_HSPACING) [expr 5*$::env(FP_PDN_CORE_RING_HWIDTH)]
-
-set ::env(VDD_NETS) [list {vccd1} {vccd2} {vdda1} {vdda2}]
-set ::env(GND_NETS) [list {vssd1} {vssd2} {vssa1} {vssa2}]
-set ::env(SYNTH_USE_PG_PINS_DEFINES) "USE_POWER_PINS"
-
-set ::env(RUN_CVC) 0
-
-# Pin Configurations. DON'T TOUCH
-set ::env(FP_PIN_ORDER_CFG) $script_dir/pin_order.cfg
-set ::env(FP_DEF_TEMPLATE) $script_dir/../../def/user_project_wrapper_empty.def
-set ::unit 2.4
-set ::env(FP_IO_VEXTEND) [expr 2*$::unit]
-set ::env(FP_IO_HEXTEND) [expr 2*$::unit]
-set ::env(FP_IO_VLENGTH) $::unit
-set ::env(FP_IO_HLENGTH) $::unit
-
-set ::env(FP_IO_VTHICKNESS_MULT) 4
-set ::env(FP_IO_HTHICKNESS_MULT) 4
diff --git a/openlane/user_project_wrapper/gen_pdn.tcl b/openlane/user_project_wrapper/gen_pdn.tcl
new file mode 100644
index 0000000..9320fef
--- /dev/null
+++ b/openlane/user_project_wrapper/gen_pdn.tcl
@@ -0,0 +1,38 @@
+read_lef $::env(MERGED_LEF_UNPADDED)
+read_def $::env(CURRENT_DEF)
+
+set ::env(_SPACING) 1.7
+set ::env(_WIDTH) 3
+
+set power_domains [list {vccd1 vssd1} {vccd2 vssd2} {vdda1 vssa1} {vdda2 vssa2}]
+
+set ::env(_VDD_NET_NAME) vccd1
+set ::env(_GND_NET_NAME) vssd1
+
+set ::env(_V_OFFSET) 14
+set ::env(_H_OFFSET) $::env(_V_OFFSET)
+set ::env(_V_PITCH) 180
+set ::env(_H_PITCH) 180
+set ::env(_V_PDN_OFFSET) 0
+set ::env(_H_PDN_OFFSET) 0
+
+set ::env(CONNECT_GRIDS) 1
+
+foreach domain $power_domains {
+    set ::env(_VDD_NET_NAME) [lindex $domain 0]
+    set ::env(_GND_NET_NAME) [lindex $domain 1]
+
+    pdngen $::env(PDN_CFG) -verbose
+
+    # This is a hack to connect the power straps only for the first domain.
+    set ::env(CONNECT_GRIDS) 0
+
+    set ::env(_V_OFFSET) \
+        [expr $::env(_V_OFFSET) + 2*($::env(_WIDTH)+$::env(_SPACING))]
+    set ::env(_H_OFFSET) \
+        [expr $::env(_H_OFFSET) + 2*($::env(_WIDTH)+$::env(_SPACING))]
+    set ::env(_V_PDN_OFFSET) [expr $::env(_V_PDN_OFFSET)+6*$::env(_WIDTH)]
+    set ::env(_H_PDN_OFFSET) [expr $::env(_H_PDN_OFFSET)+6*$::env(_WIDTH)]
+}
+
+write_def $::env(pdn_tmp_file_tag).def
diff --git a/openlane/user_project_wrapper/interactive.tcl b/openlane/user_project_wrapper/interactive.tcl
new file mode 100644
index 0000000..0d0c354
--- /dev/null
+++ b/openlane/user_project_wrapper/interactive.tcl
@@ -0,0 +1,41 @@
+package require openlane
+set script_dir [file dirname [file normalize [info script]]]
+
+prep -design $script_dir -tag user_project_wrapper -overwrite
+set save_path $script_dir/../..
+
+verilog_elaborate
+
+init_floorplan
+
+place_io_ol
+
+add_macro_placement mprj 200 320 N
+
+manual_macro_placement f
+
+exec -ignorestderr openroad -exit $script_dir/gen_pdn.tcl
+set_def $::env(pdn_tmp_file_tag).def
+
+global_routing_or
+add_route_obs
+detailed_routing
+
+write_powered_verilog -power vccd1 -ground vssd1
+set_netlist $::env(lvs_result_file_tag).powered.v
+
+run_magic
+run_magic_spice_export
+
+save_views       -lef_path $::env(magic_result_file_tag).lef \
+                 -def_path $::env(tritonRoute_result_file_tag).def \
+                 -gds_path $::env(magic_result_file_tag).gds \
+                 -mag_path $::env(magic_result_file_tag).mag \
+                 -save_path $save_path \
+                 -tag $::env(RUN_TAG)
+
+run_magic_drc
+
+run_lvs; # requires run_magic_spice_export
+
+run_antenna_check
diff --git a/openlane/user_project_wrapper/pdn.tcl b/openlane/user_project_wrapper/pdn.tcl
new file mode 100644
index 0000000..b7e5f09
--- /dev/null
+++ b/openlane/user_project_wrapper/pdn.tcl
@@ -0,0 +1,66 @@
+# Power nets
+set ::power_nets $::env(_VDD_NET_NAME)
+set ::ground_nets $::env(_GND_NET_NAME)
+
+pdngen::specify_grid stdcell {
+    name grid
+	core_ring {
+		met5 {width $::env(_WIDTH) spacing $::env(_SPACING) core_offset $::env(_H_OFFSET)}
+		met4 {width $::env(_WIDTH) spacing $::env(_SPACING) core_offset $::env(_V_OFFSET)}
+	}
+	rails {
+	}
+    straps {
+	    met4 {width $::env(_WIDTH) pitch $::env(_V_PITCH) offset $::env(_V_PDN_OFFSET)}
+	    met5 {width $::env(_WIDTH) pitch $::env(_H_PITCH) offset $::env(_H_PDN_OFFSET)}
+    }
+    connect {{met4 met5}}
+}
+
+pdngen::specify_grid macro {
+	instance "obs_core_obs"
+    power_pins $::env(_VDD_NET_NAME)
+    ground_pins $::env(_GND_NET_NAME)
+    blockages "li1 met1 met2 met3 met4 met5"
+    straps {
+    }
+    connect {}
+}
+
+if { $::env(CONNECT_GRIDS) } {
+	pdngen::specify_grid macro {
+	    power_pins "VPWR"
+	    ground_pins "VGND"
+	    blockages "met4"
+	    straps {
+	    }
+	    connect {{met4_PIN_ver met5}}
+	}
+} else {
+	pdngen::specify_grid macro {
+	    power_pins "VPWR"
+	    ground_pins "VGND"
+	    blockages "met4"
+	    straps {
+	    }
+	    connect {}
+	}
+}
+
+pdngen::specify_grid macro {
+    power_pins $::env(_VDD_NET_NAME)
+    ground_pins $::env(_GND_NET_NAME)
+    blockages "li1 met1 met2 met3 met4"
+    straps {
+    }
+    connect {}
+}
+
+set ::halo 0
+
+# POWER or GROUND #Std. cell rails starting with power or ground rails at the bottom of the core area
+set ::rails_start_with "POWER" ;
+
+# POWER or GROUND #Upper metal stripes starting with power or ground rails at the left/bottom of the core area
+set ::stripes_start_with "POWER" ;
+