clean-up
diff --git a/.gitmodules b/.gitmodules
index e69de29..7c54d50 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "verilog/rtl/yifive/ycr1c"]
+	path = verilog/rtl/yifive/ycr1c
+	url = https://github.com/dineshannayya/ycr1c.git
+[submodule "verilog/rtl/qspim"]
+	path = verilog/rtl/qspim
+	url = https://github.com/dineshannayya/qspim.git
diff --git a/hacks/patch/pdngen.patch b/hacks/patch/pdngen.patch
new file mode 100644
index 0000000..8d44ec8
--- /dev/null
+++ b/hacks/patch/pdngen.patch
@@ -0,0 +1,27 @@
+diff --git a/src/pdn/src/PdnGen.tcl b/src/pdn/src/PdnGen.tcl
+index 7322d54ad..7f178167f 100644
+--- a/src/pdn/src/PdnGen.tcl
++++ b/src/pdn/src/PdnGen.tcl
+@@ -966,8 +966,9 @@ proc add_pdn_ring {args} {
+ 
+   if {[dict exists $args -grid]} {
+     set current_grid [check_grid [get_grid [dict get $args -grid]]]
++    #Dinesh-A: Core Ring without Strap
++    set grid $current_grid
+   }
+-  set grid $current_grid
+   set layers [check_layer_names [dict get $args -layers]]
+ 
+   set process_args $args
+@@ -6686,7 +6687,10 @@ proc plan_grid {} {
+   }
+ 
+   add_grid
+-  process_channels
++  #Dinesh-A: Core Ring without Strap
++  if {[info exists $grid_data]} {
++  	process_channels
++  }
+ 
+   foreach pwr_net [dict get $design_data power_nets] {
+     generate_grid_vias "POWER" $pwr_net
diff --git a/hacks/patch/resizer.patch b/hacks/patch/resizer.patch
index 44e2796..bf587c7 100644
--- a/hacks/patch/resizer.patch
+++ b/hacks/patch/resizer.patch
@@ -1,8 +1,8 @@
 diff --git a/src/rsz/src/Resizer.cc b/src/rsz/src/Resizer.cc
-index b57fce674..cac938c44 100644
+index 3cbe306e3..300c6c476 100644
 --- a/src/rsz/src/Resizer.cc
 +++ b/src/rsz/src/Resizer.cc
-@@ -1685,9 +1685,11 @@ Resizer::resizeToTargetSlew(const Pin *drvr_pin,
+@@ -1691,9 +1691,11 @@ Resizer::resizeToTargetSlew(const Pin *drvr_pin,
      ensureWireParasitic(drvr_pin);
      // Includes net parasitic capacitance.
      float load_cap = graph_delay_calc_->loadCap(drvr_pin, tgt_slew_dcalc_ap_);
@@ -15,15 +15,3 @@
          debugPrint(logger_, RSZ, "resize", 2, "{} {} -> {}",
                     sdc_network_->pathName(drvr_pin),
                     cell->name(),
-@@ -2500,8 +2502,10 @@ Resizer::repairSetup(PathRef &path,
-         prev_drive = 0.0;
-       LibertyCell *upsize = upsizeCell(in_port, drvr_port, load_cap,
-                                        prev_drive, dcalc_ap);
--      if (upsize) {
-+      // DINESH-A: delay cells resize disabled
-+      if (upsize && (strncmp(drvr_port->libertyCell()->name(),"sky130_fd_sc_hd__clkdlybuf4s15_2",26) != 0)) {
-         Instance *drvr = network_->instance(drvr_pin);
-+	//printf("Dinesh-A: Upsizing the cells: %s %s %s\n",network_->pathName(drvr_pin),drvr_port->libertyCell()->name(),upsize->name());
-         debugPrint(logger_, RSZ, "repair_setup", 2, "resize {} {} -> {}",
-                    network_->pathName(drvr_pin),
-                    drvr_port->libertyCell()->name(),
diff --git a/hacks/patch/scan_swap.patch b/hacks/patch/scan_swap.patch
index 41d98b7..e69de29 100644
--- a/hacks/patch/scan_swap.patch
+++ b/hacks/patch/scan_swap.patch
@@ -1,60 +0,0 @@
-diff --git a/src/sta/network/ConcreteNetwork.cc b/src/sta/network/ConcreteNetwork.cc
-index 6f8b842..8096f2e 100644
---- a/src/sta/network/ConcreteNetwork.cc
-+++ b/src/sta/network/ConcreteNetwork.cc
-@@ -1180,11 +1180,14 @@ ConcreteNetwork::replaceCell(Instance *inst,
-   ConcreteCell *ccell = reinterpret_cast<ConcreteCell*>(cell);
-   int port_count = ccell->portBitCount();
-   ConcreteInstance *cinst = reinterpret_cast<ConcreteInstance*>(inst);
-+  // Port count picked from Instance instead of Target cells-Dinesh A
-+  ConcreteCell *instcell = reinterpret_cast<ConcreteCell*>(cinst->cell());
-+  int inst_port_count = instcell->portBitCount();
-   ConcretePin **pins = cinst->pins_;
-   ConcretePin **rpins = new ConcretePin*[port_count];
--  for (int i = 0; i < port_count; i++)
--    rpins[i] = nullptr;
--  for (int i = 0; i < port_count; i++) {
-+  for (int i = 0; i < port_count; i++) 
-+    rpins[i] = pins[inst_port_count-1];
-+  for (int i = 0; i < inst_port_count; i++) {
-     ConcretePin *cpin = pins[i];
-     if (cpin) {
-       ConcretePort *pin_cport = reinterpret_cast<ConcretePort*>(cpin->port());
-diff --git a/src/sta/tcl/NetworkEdit.tcl b/src/sta/tcl/NetworkEdit.tcl
-index 5ce616b..bdd4057 100644
---- a/src/sta/tcl/NetworkEdit.tcl
-+++ b/src/sta/tcl/NetworkEdit.tcl
-@@ -236,6 +236,21 @@ proc replace_cell { instance lib_cell } {
-   }
- }
- 
-+proc replace_to_scan_cell { instance } {
-+  set inst [get_instance_error "instance" $instance]
-+  set inst_cell [get_full_name [$inst liberty_cell]]
-+  #Target scan cell __d to __sd
-+  #example sky130_fd_sc_hd__dfrtp_2 to sky130_fd_sc_hd__sdfrtp_2
-+  set inst_scell [regsub -all "__d" $inst_cell "__sd"]
-+  puts "Info: Scan Swapping => Instance:$instance From:$inst_cell to:$inst_scell";
-+  if { $inst_cell == "NULL"} {
-+    return 0
-+  }
-+  set scell [get_lib_cell_warn "lib_cell" $inst_scell]
-+  replace_cell_cmd $inst $scell
-+  return 1
-+}
-+
- proc path_regexp {} {
-   global hierarchy_separator
-   set id_regexp "\[^${hierarchy_separator}\]+"
-diff --git a/src/sta/tcl/Sta.tcl b/src/sta/tcl/Sta.tcl
-index f3de994..a6e8e34 100644
---- a/src/sta/tcl/Sta.tcl
-+++ b/src/sta/tcl/Sta.tcl
-@@ -623,6 +623,7 @@ define_cmd_args "make_instance" {inst_path lib_cell}
- define_cmd_args "make_net" {}
- 
- define_cmd_args "replace_cell" {instance lib_cell}
-+define_cmd_args "replace_to_scan_cell" {instance}
- 
- define_cmd_args "insert_buffer" {buffer_name buffer_cell net load_pins\
- 				       buffer_out_net_name}
diff --git a/hacks/src/OpenROAD/PdnGen.tcl b/hacks/src/OpenROAD/PdnGen.tcl
new file mode 100644
index 0000000..7f17816
--- /dev/null
+++ b/hacks/src/OpenROAD/PdnGen.tcl
@@ -0,0 +1,6751 @@
+#BSD 3-Clause License
+#
+#Copyright (c) 2019, The Regents of the University of California
+#All rights reserved.
+#
+#Redistribution and use in source and binary forms, with or without
+#modification, are permitted provided that the following conditions are met:
+#
+#1. Redistributions of source code must retain the above copyright notice, this
+#   list of conditions and the following disclaimer.
+#
+#2. Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+#
+#3. Neither the name of the copyright holder nor the names of its
+#   contributors may be used to endorse or promote products derived from
+#   this software without specific prior written permission.
+#
+#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+#AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+#IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+#DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+#FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+#DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+#SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+#CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+#OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+#OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+sta::define_cmd_args "pdngen" {[-verbose] [config_file]}
+
+proc pdngen { args } {
+  sta::parse_key_args "pdngen" args \
+    keys {} flags {-verbose}
+
+  if {[info exists flags(-verbose)]} {
+    pdngen::set_verbose
+  }
+
+  if {[llength $args] > 0} {
+    set config_file [file nativename [lindex $args 0]]
+    pdngen::apply $config_file
+  } else {
+    pdngen::apply
+  }
+}
+
+sta::define_cmd_args "add_global_connection" {-net <net_name> \
+                                              -inst_pattern <inst_name_pattern> \ 
+                                              -pin_pattern <pin_name_pattern> \
+                                              [(-power|-ground)]}
+
+proc add_global_connection {args} {
+  if {[ord::get_db_block] == "NULL"} {
+    utl::error PDN 91 "Design must be loaded before calling the add_global_connection command."
+  }
+
+  sta::parse_key_args "add_global_connection" args \
+    keys {-net -inst_pattern -pin_pattern} \
+    flags {-power -ground}
+
+  if {[llength $args] > 0} {
+    utl::error PDN 131 "Unexpected argument [lindex $args 0] for add_global_connection command."
+  }
+
+  if {[info exists flags(-power)] && [info exists flags(-ground)]} {
+    utl::error PDN 92 "The flags -power and -ground of the add_global_connection command are mutually exclusive."
+  }
+
+  if {![info exists keys(-net)]} {
+    utl::error PDN 93 "The -net option of the add_global_connection command is required."
+  }
+
+  if {![info exists keys(-inst_pattern)]} {
+    set keys(-inst_pattern) {.*}
+  } else {
+    if {[catch {regexp $keys(-inst_pattern) ""}]} {
+      utl::error PDN 142 "The -inst_pattern argument ($keys(-inst_pattern)) is not a valid regular expression."
+    }
+  }
+
+  if {![info exists keys(-pin_pattern)]} { 
+    utl::error PDN 94 "The -pin_pattern option of the add_global_connection command is required."
+  } else {
+    if {[catch {regexp $keys(-pin_pattern) ""}]} {
+      utl::error PDN 157 "The -pin_pattern argument ($keys(-pin_pattern)) is not a valid regular expression."
+    }
+  }
+
+  if {[info exists flags(-power)]} {
+    if {[set net [[ord::get_db_block] findNet $keys(-net)]] == "NULL"} {
+      set net [odb::dbNet_create [ord::get_db_block] $keys(-net)]
+    }
+    $net setSpecial
+    $net setSigType POWER
+    pdngen::check_power $keys(-net)
+    pdngen::add_power_net $keys(-net)
+  }
+
+  if {[info exists flags(-ground)]} {
+    if {[set net [[ord::get_db_block] findNet $keys(-net)]] == "NULL"} {
+      set net [odb::dbNet_create [ord::get_db_block] $keys(-net)]
+    }
+    $net setSpecial
+    $net setSigType GROUND
+    pdngen::check_ground $keys(-net)
+    pdngen::add_ground_net $keys(-net)
+  }
+
+  dict lappend pdngen::global_connections $keys(-net) [list inst_name $keys(-inst_pattern) pin_name $keys(-pin_pattern)]
+
+  if {[set net [[ord::get_db_block] findNet $keys(-net)]] == "NULL"} {
+    utl::warn PDN 167 "Net created for $keys(-net), if intended as power or ground net add the -power/-ground switch as appropriate."
+    set net [odb::dbNet_create [ord::get_db_block] $keys(-net)]
+  }
+  pdn::add_global_connect $keys(-inst_pattern) $keys(-pin_pattern) $net
+  pdn::global_connect [ord::get_db_block]
+}
+
+# define_pdn_grid -name main_grid -pins {metal7} -voltage_domains {CORE VIN}
+# define_pdn_grid -macro -name ram -orient {R0 R180 MX MY} -starts_with POWER -pin_direction vertical -block metal6
+
+sta::define_cmd_args "define_pdn_grid" {[-name <name>] \
+                                        [-macro] \
+                                        [-grid_over_pg_pins|-grid_over_boundary] \
+                                        [-voltage_domains <list_of_voltage_domains>] \
+                                        [-orient <list_of_valid_orientations>] \
+                                        [-instances <list_of_instances>] \
+                                        [-cells <list_of_cell_names> ] \
+                                        [-halo <list_of_halo_values>] \
+                                        [-pin_direction (horizontal|vertical)] \
+                                        [-pins <list_of_pin_layers>] \
+                                        [-starts_with (POWER|GROUND)]}
+
+proc define_pdn_grid {args} {
+  pdngen::check_design_state
+
+  sta::parse_key_args "define_pdn_grid" args \
+    keys {-name -voltage_domains -orient -instances -cells -halo -pin_direction -pins -starts_with} \
+    flags {-macro -grid_over_pg_pins -grid_over_boundary}
+
+  if {[llength $args] > 0} {
+    utl::error PDN 132 "Unexpected argument [lindex $args 0] for define_pdn_grid command."
+  }
+
+  if {[info exists flags(-macro)]} {
+    set keys(-macro) 1
+
+    if {[info exists flags(-grid_over_pg_pins)] && [info exists flags(-grid_over_bounary)]} {
+      utl::error PDN 175 "Options -grid_over_pg_pins and -grid_over_boundary are mutually exclusive."
+    }
+
+    set keys(-grid_over_pg_pins) 1
+    if {[info exists flags(-grid_over_boundary)]} {
+      set keys(-grid_over_pg_pins) 0
+    }
+  }
+
+  if {[llength $args] > 0} {
+    utl::error PDN 73 "Unrecognized argument [lindex $args 0] for define_pdn_grid."
+  }
+
+  if {[info exists keys(-halo)]} {
+    if {[llength $keys(-halo)] == 1} {
+      set keys(-halo) [list $keys(-halo) $keys(-halo) $keys(-halo) $keys(-halo)]
+    } elseif {[llength $keys(-halo)] == 2} {
+      set keys(-halo) [list {*}$keys(-halo) {*}$keys(-halo)]
+    } elseif {[llength $keys(-halo)] != 4} {
+      utl::error PDN 163 "Argument -halo of define_pdn_grid must consist of 1, 2 or 4 entries."
+    }
+  }
+  pdngen::define_pdn_grid {*}[array get keys]
+}
+
+# set_voltage_domain -name CORE -power_net VDD  -ground_net VSS
+# set_voltage_domain -name VIN  -region_name TEMP_ANALOG -power_net VPWR -ground_net VSS
+
+sta::define_cmd_args "set_voltage_domain" {-name domain_name \
+                                           [-region region_name] \
+                                           -power power_net_name \
+                                           [-secondary_power secondary_power_net_name] \
+                                           -ground ground_net_name}
+
+proc set_voltage_domain {args} {
+  pdngen::check_design_state
+
+  sta::parse_key_args "set_voltage_domain" args \
+    keys {-name -region -power -secondary_power -ground}
+
+  if {[llength $args] > 0} {
+    utl::error PDN 133 "Unexpected argument [lindex $args 0] for set_voltage_domain command."
+  }
+
+  if {![info exists keys(-name)]} {
+    utl::error PDN 97 "The -name argument is required."
+  }
+
+  if {![info exists keys(-power)]} {
+    utl::error PDN 98 "The -power argument is required."
+  }
+
+  if {![info exists keys(-ground)]} {
+    utl::error PDN 99 "The -ground argument is required."
+  }
+
+  if {[llength $args] > 0} {
+    utl::error PDN 120 "Unrecognized argument [lindex $args 0] for set_voltage_domain."
+  }
+
+  pdngen::set_voltage_domain {*}[array get keys]
+}
+
+# add_pdn_stripe -grid main_grid -layer metal1 -width 0.17 -followpins
+# add_pdn_stripe -grid main_grid -layer metal2 -width 0.17 -followpins
+# add_pdn_stripe -grid main_grid -layer metal4 -width 0.48 -pitch 56.0 -offset 2 -starts_with POWER
+# add_pdn_stripe -grid main_grid -layer metal7 -width 1.40 -pitch 40.0 -offset 2 -starts_with POWER
+sta::define_cmd_args "add_pdn_stripe" {[-grid grid_name] \
+                                       -layer layer_name \
+                                       -width width_value \
+                                       [-followpins] \
+                                       [-extend_to_core_ring] \
+                                       [-pitch pitch_value] \
+                                       [-spacing spacing_value] \
+                                       [-offset offset_value] \
+                                       [-starts_width (POWER|GROUND)]}
+
+proc add_pdn_stripe {args} {
+  pdngen::check_design_state
+
+  sta::parse_key_args "add_pdn_stripe" args \
+    keys {-grid -layer -width -pitch -spacing -offset -starts_with} \
+    flags {-followpins -extend_to_core_ring}
+
+  if {[llength $args] > 0} {
+    utl::error PDN 134 "Unexpected argument [lindex $args 0] for add_pdn_stripe command."
+  }
+
+  if {![info exists keys(-layer)]} {
+    utl::error PDN 100 "The -layer argument is required."
+  }
+
+  if {![info exists keys(-width)]} {
+    utl::error PDN 101 "The -width argument is required."
+  }
+
+  if {![info exists flags(-followpins)] && ![info exists keys(-pitch)]} {
+    utl::error PDN 102 "The -pitch argument is required for non-followpins stripes."
+  }
+
+  if {[info exists flags(-followpins)]} {
+    set keys(stripe) rails
+  } else {
+    set keys(stripe) straps
+  }
+
+  if {[info exists flags(-extend_to_core_ring)]} {
+    set keys(-extend_to_core_ring) 1
+  }
+
+  pdngen::add_pdn_stripe {*}[array get keys]
+}
+
+# add_pdn_ring   -grid main_grid -layer metal6 -width 5.0 -spacing  3.0 -core_offset 5
+# add_pdn_ring   -grid main_grid -layer metal7 -width 5.0 -spacing  3.0 -core_offset 5
+
+sta::define_cmd_args "add_pdn_ring" {[-grid grid_name] \
+                                     -layers list_of_2_layer_names \
+                                     -widths (width_value|list_of_width_values) \
+                                     -spacings (spacing_value|list_of_spacing_values) \
+                                     [-core_offsets (offset_value|list_of_offset_values)] \
+                                     [-pad_offsets (offset_value|list_of_offset_values)] \
+                                     [-power_pads list_of_core_power_padcells] \
+                                     [-ground_pads list_of_core_ground_padcells]}
+
+proc add_pdn_ring {args} {
+  pdngen::check_design_state
+
+  sta::parse_key_args "add_pdn_ring" args \
+    keys {-grid -layers -widths -spacings -core_offsets -pad_offsets -power_pads -ground_pads} 
+
+  if {[llength $args] > 0} {
+    utl::error PDN 135 "Unexpected argument [lindex $args 0] for add_pdn_ring command."
+  }
+
+  if {![info exists keys(-layers)]} {
+    utl::error PDN 103 "The -layers argument is required."
+  }
+
+  if {[llength $keys(-layers)] != 2} {
+    utl::error PDN 137 "Expecting a list of 2 elements for -layers option of add_pdn_ring command, found [llength $keys(-layers)]."
+  }
+
+  if {![info exists keys(-widths)]} {
+    utl::error PDN 104 "The -widths argument is required."
+  }
+
+  if {![info exists keys(-spacings)]} {
+    utl::error PDN 105 "The -spacings argument is required."
+  }
+
+  if {[info exists keys(-core_offsets)] && [info exists keys(-pad_offsets)]} {
+    utl::error PDN 106 "Only one of -pad_offsets or -core_offsets can be specified."
+  }
+
+  if {![info exists keys(-core_offsets)] && ![info exists keys(-pad_offsets)]} {
+    utl::error PDN 107 "One of -pad_offsets or -core_offsets must be specified."
+  }
+
+  if {[info exists keys(-pad_offsets)]} {
+    if {![info exists keys(-power_pads)]} {
+      utl::error PDN 143 "The -power_pads option is required when the -pad_offsets option is used."
+    }
+
+    if {![info exists keys(-ground_pads)]} {
+      utl::error PDN 144 "The -ground_pads option is required when the -pad_offsets option is used."
+    }
+  } else {
+    if {[info exists keys(-power_pads)] || [info exists keys(-ground_pads)]} {
+      utl::warn PDN 145 "Options -power_pads and -ground_pads are only used when the -pad_offsets option is specified."
+    }
+  }
+
+  pdngen::add_pdn_ring {*}[array get keys]
+}
+
+sta::define_cmd_args "add_pdn_connect" {[-grid grid_name] \
+                                        -layers list_of_2_layers \
+                                        [-cut_pitch pitch_value] \
+                                        [-fixed_vias list_of_vias]}
+
+# add_pdn_connect -grid main_grid -layers {metal1 metal2} -cut_pitch 0.16
+# add_pdn_connect -grid main_grid -layers {metal2 metal4}
+# add_pdn_connect -grid main_grid -layers {metal4 metal7}
+
+proc add_pdn_connect {args} {
+  pdngen::check_design_state
+
+  sta::parse_key_args "add_pdn_connect" args \
+    keys {-grid -layers -cut_pitch -fixed_vias} \
+
+  if {[llength $args] > 0} {
+    utl::error PDN 136 "Unexpected argument [lindex $args 0] for add_pdn_connect command."
+  }
+
+  if {![info exists keys(-layers)]} {
+    utl::error PDN 108 "The -layers argument is required."
+  }
+
+  pdngen::add_pdn_connect {*}[array get keys]
+}
+
+namespace eval pdngen {
+variable block_masters {}
+variable logical_viarules {}
+variable physical_viarules {}
+variable vias {}
+variable stripe_locs
+variable layers {}
+variable block
+variable tech
+variable libs
+variable design_data {}
+variable default_grid_data {}
+variable def_output
+variable widths
+variable pitches
+variable loffset
+variable boffset
+variable site
+variable row_height
+variable metal_layers {}
+variable blockages {}
+variable padcell_blockages {}
+variable instances {}
+variable default_template_name {}
+variable template {}
+variable default_cutclass {}
+variable twowidths_table {}
+variable twowidths_table_wrongdirection {}
+variable stdcell_area ""
+variable power_nets {}
+variable ground_nets {}
+variable macros {}
+variable verbose 0
+variable global_connections {}
+variable default_global_connections {
+  VDD {
+    {inst_name .* pin_name ^VDD$}
+    {inst_name .* pin_name ^VDDPE$}
+    {inst_name .* pin_name ^VDDCE$}
+  }
+  VSS {
+    {inst_name .* pin_name ^VSS$}
+    {inst_name .* pin_name ^VSSE$}
+  }
+}
+variable voltage_domains {
+  CORE {
+    primary_power VDD primary_ground VSS
+  }
+}
+
+proc check_design_state {} {
+  if {[ord::get_db_block] == "NULL"} {
+    utl::error PDN 72 "Design must be loaded before calling pdngen commands."
+  }
+}
+
+proc check_orientations {orientations} {
+  set valid_orientations {R0 R90 R180 R270 MX MY MXR90 MYR90}
+  set lef_orientations {N R0 FN MY S R180 FS MX E R270 FE MYR90 W R90 FW MXR90}
+
+  set checked_orientations {}
+  foreach orient $orientations {
+    if {[lsearch -exact $valid_orientations $orient] > -1} {
+      lappend checked_orientations $orient
+    } elseif {[dict exists $lef_orientations $orient]} {
+      lappend checked_orientations [dict get $lef_orientations $orient]
+    } else {
+      utl::error PDN 74 "Invalid orientation $orient specified, must be one of [join $valid_orientations {, }]."
+    }
+  }
+  return $checked_orientations
+}
+
+proc check_layer_names {layer_names} {
+  set tech [ord::get_db_tech]
+
+  foreach layer_name $layer_names {
+    if {[$tech findLayer $layer_name] == "NULL"} {
+      if {[regexp {(.*)_PIN_(hor|ver)$} $layer_name - actual_layer_name]} {
+        if {[$tech findLayer $actual_layer_name] == "NULL"} {
+          utl::error "PDN" 75 "Layer $actual_layer_name not found in loaded technology data."
+        }
+      } else {
+        utl::error "PDN" 76 "Layer $layer_name not found in loaded technology data."
+      }
+    }  
+  }
+  return $layer_names
+}
+
+proc check_layer_width {layer_name width} {
+  set tech [ord::get_db_tech]
+
+  set layer [$tech findLayer $layer_name]
+  set minWidth [$layer getMinWidth]
+  set maxWidth [$layer getMaxWidth]
+
+  if {[ord::microns_to_dbu $width] < $minWidth} {
+    utl::error "PDN" 77 "Width ($width) specified for layer $layer_name is less than minimum width ([ord::dbu_to_microns $minWidth])."
+  }
+  if {[ord::microns_to_dbu $width] > $maxWidth} {
+    utl::error "PDN" 78 "Width ($width) specified for layer $layer_name is greater than maximum width ([ord::dbu_to_microns $maxWidth])."
+  }
+  return $width
+}
+
+proc check_layer_spacing {layer_name spacing} {
+  set tech [ord::get_db_tech]
+
+  set layer [$tech findLayer $layer_name]
+  set minSpacing [$layer getSpacing]
+
+  if {[ord::microns_to_dbu $spacing] < $minSpacing} {
+    utl::error "PDN" 79 "Spacing ($spacing) specified for layer $layer_name is less than minimum spacing ([ord::dbu_to_microns $minSpacing)]."
+  }
+  return $spacing
+}
+
+proc check_rails {rails_spec} {
+  if {[llength $rails_spec] % 2 == 1} {
+    utl::error "PDN" 81 "Expected an even number of elements in the list for -rails option, got [llength $rails_spec]."
+  }
+  check_layer_names [dict keys $rails_spec]
+  foreach layer_name [dict keys $rails_spec] {
+    if {[dict exists $rails_spec $layer_name width]} {
+      check_layer_width $layer_name [dict get $rails_spec $layer_name width]
+    }
+    if {[dict exists $rails_spec $layer_name spacing]} {
+      check_layer_spacing $layer_name [dict get $rails_spec $layer_name spacing]
+    }
+    if {![dict exists $rails_spec $layer_name pitch]} {
+      dict set rails_spec $layer_name pitch [ord::dbu_to_microns [expr [get_row_height] * 2]]
+    }
+  }
+  return $rails_spec
+}
+
+proc check_straps {straps_spec} {
+  if {[llength $straps_spec] % 2 == 1} {
+    utl::error "PDN" 83 "Expected an even number of elements in the list for straps specification, got [llength $straps_spec]."
+  }
+  check_layer_names [dict keys $straps_spec]
+  foreach layer_name [dict keys $straps_spec] {
+    if {[dict exists $straps_spec $layer_name width]} {
+      check_layer_width $layer_name [dict get $straps_spec $layer_name width]
+    } else {
+      utl::error PDN 84 "Missing width specification for strap on layer $layer_name."
+    }
+    set width [ord::microns_to_dbu [dict get $straps_spec $layer_name width]]
+
+    if {![dict exists $straps_spec $layer_name spacing]} {
+      dict set straps_spec $layer_name spacing [expr [dict get $straps_spec $layer_name pitch] / 2.0]
+    }
+    check_layer_spacing $layer_name [dict get $straps_spec $layer_name spacing]
+    set spacing [ord::microns_to_dbu [dict get $straps_spec $layer_name spacing]]
+
+    if {[dict exists $straps_spec $layer_name pitch]} {
+      set layer [[ord::get_db_tech] findLayer $layer_name]
+      set minPitch [expr 2 * ([$layer getSpacing] + $width)]
+      if {[ord::microns_to_dbu [dict get $straps_spec $layer_name pitch]] < $minPitch} {
+        utl::error "PDN" 85 "Pitch [dict get $straps_spec $layer_name pitch] specified for layer $layer_name is less than 2 x (width + spacing) (width=[ord::dbu_to_microns $width], spacing=[ord::dbu_to_microns $spacing])."
+      }
+    } else {
+      utl::error PDN 86 "No pitch specified for strap on layer $layer_name."
+    }
+  }
+  return $straps_spec
+}
+
+proc check_connect {grid connect_spec} {
+  foreach connect_statement $connect_spec {
+    if {[llength $connect_statement] < 2} {
+      utl::error PDN 87 "Connect statement must consist of at least 2 entries."
+    }
+    check_layer_names [lrange $connect_statement 0 1]
+    dict set layers [lindex $connect_statement 0] 1
+    dict set layers [lindex $connect_statement 1] 1
+  }
+
+  if {[dict get $grid type] == "macro"} {
+    set pin_layer_defined 0
+    set actual_layers {}
+    foreach layer_name [dict keys $layers] {
+      if {[regexp {(.*)_PIN_(hor|ver)$} $layer_name - layer]} {
+        lappend actual_layers $layer
+      } else {
+        lappend actual_layers $layer_name
+      }
+    }
+  }
+  return $connect_spec
+}
+
+proc check_core_ring {core_ring_spec} {
+  if {[llength $core_ring_spec] % 2 == 1} {
+    utl::error "PDN" 109 "Expected an even number of elements in the list for core_ring specification, got [llength $core_ring_spec]."
+  }
+  set layer_directions {}
+  check_layer_names [dict keys $core_ring_spec]
+  foreach layer_name [dict keys $core_ring_spec] {
+    if {[dict exists $core_ring_spec $layer_name width]} {
+      check_layer_width $layer_name [dict get $core_ring_spec $layer_name width]
+    } else {
+      utl::error PDN 121 "Missing width specification for strap on layer $layer_name."
+    }
+    set width [ord::microns_to_dbu [dict get $core_ring_spec $layer_name width]]
+
+    if {![dict exists $core_ring_spec $layer_name spacing]} {
+      dict set core_ring_spec $layer_name spacing [expr [dict get $core_ring_spec $layer_name pitch] / 2.0]
+    }
+    check_layer_spacing $layer_name [dict get $core_ring_spec $layer_name spacing]
+    set spacing [ord::microns_to_dbu [dict get $core_ring_spec $layer_name spacing]]
+    dict set layer_directions [get_dir $layer_name] $layer_name
+
+    if {[dict exists $core_ring_spec $layer_name core_offset]} {
+      check_layer_spacing $layer_name [dict get $core_ring_spec $layer_name core_offset]
+    } elseif {[dict exists $core_ring_spec $layer_name pad_offset]} {
+      check_layer_spacing $layer_name [dict get $core_ring_spec $layer_name pad_offset]
+    } else {
+      utl::error PDN 146 "Must specifu a pad_offset or core_offset for rings."
+    }
+  }
+  if {[llength [dict keys $layer_directions]] == 0} {
+    utl::error PDN 139 "No direction defiend for layers [dict keys $core_ring_spec]." 
+  } elseif {[llength [dict keys $layer_directions]] == 1} {
+    set dir [dict keys $layer_directions]
+    set direction [expr $dir == "ver" ? "vertical" : "horizontal"]
+    set missing_direction [expr $dir == "ver" ? "horizontal" : "vertical"]
+    
+    utl::error PDN 140 "Layers [dict keys $core_ring_spec] are both $direction, missing layer in direction $other_direction." 
+  } elseif {[llength [dict keys $layer_directions]] > 2} {
+    utl::error PDN 141 "Unexpected number of directions found for layers [dict keys $core_ring_spec], ([dict keys $layer_directions])." 
+  }
+
+  return $core_ring_spec
+}
+
+proc check_starts_with {value} {
+  if {$value != "POWER" && $value != "GROUND"} {
+    utl::error PDN 95 "Value specified for -starts_with option ($value), must be POWER or GROUND."
+  }
+
+  return $value
+}
+
+proc check_voltage_domains {domains} {
+  variable voltage_domains
+
+  foreach domain $domains {
+    if {[lsearch [dict keys $voltage_domains] $domain] == -1} {
+      utl::error PDN 110 "Voltage domain $domain has not been specified, use set_voltage_domain to create this voltage domain."
+    }
+  }
+
+  return $domains
+}
+
+proc check_instances {instances} {
+  variable $block
+
+  foreach instance $instances {
+    if {[$block findInst $instance] == "NULL"} {
+      utl::error PDN 111 "Instance $instance does not exist in the design."
+    }
+  }
+
+  return $instances
+}
+
+proc check_cells {cells} {
+  foreach cell $cells {
+    if {[[ord::get_db] findMaster $cell] == "NULL"} {
+      utl::warn PDN 112 "Cell $cell not loaded into the database."
+    }
+  }
+
+  return $cells
+}
+
+proc check_region {region_name} {
+  set block [ord::get_db_block]
+
+  if {[$block findRegion $region_name] == "NULL"} {
+    utl::error PDN 127 "No region $region_name found in the design for voltage_domain."
+  }
+
+  return $region_name
+}
+
+proc check_power {power_net_name} {
+  set block [ord::get_db_block]
+
+  if {[set net [$block findNet $power_net_name]] == "NULL"} {
+    set net [odb::dbNet_create $block $power_net_name]
+    $net setSpecial
+    $net setSigType "POWER"
+  } else {
+    if {[$net getSigType] != "POWER"} {
+      utl::error PDN 128 "Net $power_net_name already exists in the design, but is of signal type [$net getSigType]."
+    }
+  }
+  return $power_net_name
+}
+
+proc check_secondary_power {secondary_power_net_name} {
+  set block [ord::get_db_block]
+
+  foreach secondary_power $secondary_power_net_name {
+    if {[set net [$block findNet $secondary_power]] == "NULL"} {
+      set net [odb::dbNet_create $block $secondary_power]
+      $net setSpecial
+      $net setSigType "POWER"
+    } else {
+      if {[$net getSigType] != "POWER"} {
+        utl::error PDN 176 "Net $secondary_power already exists in the design, but is of signal type [$net getSigType]."
+      }
+    }
+  }
+  return $secondary_power_net_name
+}
+
+proc check_ground {ground_net_name} {
+  set block [ord::get_db_block]
+
+  if {[set net [$block findNet $ground_net_name]] == "NULL"} {
+    set net [odb::dbNet_create $block $ground_net_name]
+    $net setSpecial
+    $net setSigType "GROUND"
+  } else {
+    if {[$net getSigType] != "GROUND"} {
+      utl::error PDN 129 "Net $ground_net_name already exists in the design, but is of signal type [$net getSigType]."
+    }
+  }
+  return $ground_net_name
+}
+
+proc set_voltage_domain {args} {
+  variable voltage_domains
+
+  set voltage_domain {}
+  set process_args $args
+  while {[llength $process_args] > 0} {
+    set arg [lindex $process_args 0]
+    set value [lindex $process_args 1]
+
+    switch $arg {
+      -name            {dict set voltage_domain name $value}
+      -power           {dict set voltage_domain primary_power [check_power $value]}
+      -secondary_power {dict set voltage_domain secondary_power [check_secondary_power $value]}
+      -ground          {dict set voltage_domain primary_ground [check_ground $value]}
+      -region          {dict set voltage_domain region [check_region $value]}
+      default          {utl::error PDN 130 "Unrecognized argument $arg, should be one of -name, -power, -ground -region."}
+    }
+
+    set process_args [lrange $process_args 2 end]
+  }
+  dict set voltage_domains [dict get $voltage_domain name] $voltage_domain
+}
+
+proc check_direction {direction} {
+  if {$direction != "horizontal" && $direction != "vertical"} {
+    utl::error PDN 138 "Unexpected value for direction ($direction), should be horizontal or vertical."
+  }
+  return $direction
+}
+
+proc check_number {value} {
+  if {![string is double $value]} {
+    error "value ($value) not recognized as a number."
+  }
+
+  return $value
+}
+
+proc check_halo {value} {
+  foreach item $value {
+    if {[catch {check_number $item} msg]} {
+      utl::error PDN 164 "Problem with halo specification, $msg."
+    }
+  }
+
+  return $value
+}
+
+proc define_pdn_grid {args} {
+  variable current_grid
+
+  set grid {}
+
+  set process_args $args
+  while {[llength $process_args] > 0} {
+    set arg [lindex $process_args 0]
+    set value [lindex $process_args 1]
+
+    switch $arg {
+      -name              {dict set grid name $value}
+      -voltage_domains   {dict set grid voltage_domains [check_voltage_domains $value]}
+      -macro             {dict set grid type macro}
+      -grid_over_pg_pins {dict set grid grid_over_pg_pins $value}
+      -orient            {dict set grid orient [check_orientations $value]}
+      -instances         {dict set grid instances [check_instances $value]}
+      -cells             {dict set grid macro [check_cells $value]}
+      -halo              {dict set grid halo [check_halo [lmap x $value {ord::microns_to_dbu [check_number $x]}]]}
+      -pins              {dict set grid pins [check_layer_names $value]}
+      -starts_with       {dict set grid starts_with [check_starts_with $value]}
+      -pin_direction     {dict set grid pin_direction [check_direction $value]}
+      default            {utl::error PDN 88 "Unrecognized argument $arg, should be one of -name, -orient, -instances -cells -pins -starts_with."}
+    }
+
+    set process_args [lrange $process_args 2 end]
+  }
+
+  set current_grid [verify_grid $grid]
+}
+
+proc get_grid {grid_name} {
+  variable design_data
+
+  if {[dict exists $design_data grid]} {
+    dict for {type grids} [dict get $design_data grid] {
+      dict for {name grid} $grids {
+        if {$name == $grid_name} {
+          return $grid
+        }
+      }
+    }
+  }
+
+  return {}
+}
+
+proc check_grid {grid} {
+  if {$grid == {}} {
+    utl::error PDN  113 "The grid $grid_name has not been defined."
+  }
+  return $grid
+}
+
+proc check_power_ground {value} {
+  if {$value == "POWER" || $value == "GROUND"} {
+    return $value
+  }
+  utl::error PDN 114 "Unexpected value ($value), must be either POWER or GROUND."
+}
+
+proc add_pdn_stripe {args} {
+  variable current_grid
+
+  if {[dict exists $args -grid]} {
+    set current_grid [check_grid [get_grid [dict get $args -grid]]]
+  }
+  set grid $current_grid
+
+  set stripe [dict get $args stripe]
+  set layer [check_layer_names [dict get $args -layer]]
+
+  set process_args $args
+  while {[llength $process_args] > 0} {
+    set arg [lindex $process_args 0]
+    set value [lindex $process_args 1]
+
+    switch $arg {
+      -grid            {;}
+      -layer           {;}
+      -width           {dict set grid $stripe $layer width $value}
+      -spacing         {dict set grid $stripe $layer spacing $value}
+      -offset          {dict set grid $stripe $layer offset $value}
+      -pitch           {dict set grid $stripe $layer pitch $value}
+      -starts_with     {dict set grid $stripe $layer starts_with [check_power_ground $value]}
+      -extend_to_core_ring {dict set grid $stripe $layer extend_to_core_ring 1}
+      stripe           {;}
+      default          {utl::error PDN 124 "Unrecognized argument $arg, should be one of -grid, -type, -orient, -power_pins, -ground_pins, -blockages, -rails, -straps, -connect."}
+    }
+
+    set process_args [lrange $process_args 2 end]
+  }
+
+  set current_grid [verify_grid $grid]
+}
+
+proc check_max_length {values max_length} {
+  if {[llength $values] > $max_length} {
+    error "[llength $values] provided, maximum of $max_length values allowed."
+  }
+}
+
+proc check_grid_voltage_domains {grid} {
+  if {![dict exists $grid voltage_domains]} {
+    utl::error PDN 158 "No voltage domains defined for grid."
+  }
+}
+
+proc get_voltage_domain_by_name {domain_name} {
+  variable voltage_domains
+
+  if {[dict exists $voltage_domains $domain_name]} {
+    return [dict get $voltage_domains $domain_name]
+  }
+
+  utl::error PDN 159 "Voltage domains $domain_name has not been defined."
+}
+
+proc match_inst_connection {inst net_name} {
+  variable global_connections
+
+  foreach pattern [dict get $global_connections $net_name] {
+    if {[regexp [dict get $pattern inst_name] [$inst getName]]} {
+      foreach pin [[$inst getMaster] getMTerms] {
+        if {[regexp [dict get $pattern pin_name] [$pin getName]]} {
+          return 1
+        }
+      }
+    }
+  }
+  return 0
+}
+
+proc is_inst_in_voltage_domain {inst domain_name} {
+  set voltage_domain [get_voltage_domain_by_name $domain_name]
+
+  # The instance is in the voltage domain if it connected to both related power and ground nets
+  set power_net [dict get $voltage_domain primary_power]
+  set ground_net [dict get $voltage_domain primary_ground]
+
+  return [match_inst_connection $inst $power_net] && [match_inst_connection $inst $ground_net]
+}
+
+proc get_block_inst_masters {} {
+  variable block_masters
+
+  if {[llength $block_masters] == 0} {
+    foreach inst [[ord::get_db_block] getInsts] {
+      if {[lsearch $block_masters [[$inst getMaster] getName]] == -1} {
+        lappend block_masters [[$inst getMaster] getName]
+      }
+    }
+  }
+  return $block_masters
+}
+
+proc is_cell_present {cell_name} {
+  return [lsearch [get_block_inst_masters] $cell_name] > -1
+}
+
+proc check_pwr_pads {grid cells} {
+  check_grid_voltage_domains $grid
+  set voltage_domains [dict get $grid voltage_domains]
+  set pwr_pads {}
+  set inst_example {}
+  foreach voltage_domain $voltage_domains {
+
+    set net_name [get_voltage_domain_power $voltage_domain]
+    if {[set net [[ord::get_db_block] findNet $net_name]] == "NULL"} {
+      utl::error PDN 149 "Power net $net_name not found."
+    }
+    set find_cells $cells
+    foreach inst [[ord::get_db_block] getInsts] {
+      if {[set idx [lsearch $find_cells [[$inst getMaster] getName]]] > -1} {
+        if {![is_inst_in_voltage_domain $inst $voltage_domain]} {continue}
+        # Only need one example of each cell
+        set cell_name [lindex $find_cells $idx]
+        set find_cells [lreplace $find_cells $idx $idx]
+        dict set inst_example $cell_name $inst
+      }
+      if {[llength $find_cells] == 0} {break}
+    }
+    if {[llength $find_cells] > 0} {
+      utl::warn PDN 150 "Cannot find cells ([join $find_cells {, }]) in voltage domain $voltage_domain."
+    }
+    dict for {cell inst} $inst_example {
+      set pin_name [get_inst_pin_connected_to_net $inst $net]
+      dict lappend pwr_pads $pin_name $cell
+    }
+  }
+
+  return $pwr_pads
+}
+
+proc check_gnd_pads {grid cells} {
+  check_grid_voltage_domains $grid
+  set voltage_domains [dict get $grid voltage_domains]
+  set gnd_pads {}
+  set inst_example {}
+  foreach voltage_domain $voltage_domains {
+    set net_name [get_voltage_domain_ground $voltage_domain]
+    if {[set net [[ord::get_db_block] findNet $net_name]] == "NULL"} {
+      utl::error PDN 151 "Ground net $net_name not found."
+    }
+    set find_cells $cells
+    foreach inst [[ord::get_db_block] getInsts] {
+      if {[set idx [lsearch $find_cells [[$inst getMaster] getName]]] > -1} {
+        if {![is_inst_in_voltage_domain $inst $voltage_domain]} {continue}
+        # Only need one example of each cell
+        set cell_name [lindex $find_cells $idx]
+        set find_cells [lreplace $find_cells $idx $idx]
+        dict set inst_example $cell_name $inst
+      }
+      if {[llength $find_cells] == 0} {break}
+    }
+    if {[llength $find_cells] > 0} {
+      utl::warn PDN 152 "Cannot find cells ([join $find_cells {, }]) in voltage domain $voltage_domain."
+    }
+    dict for {cell inst} $inst_example {
+      set pin_name [get_inst_pin_connected_to_net $inst $net]
+      dict lappend gnd_pads $pin_name $cell
+    }
+  }
+  return $gnd_pads
+}
+
+proc add_pdn_ring {args} {
+  variable current_grid
+
+  if {[dict exists $args -grid]} {
+    set current_grid [check_grid [get_grid [dict get $args -grid]]]
+    #Dinesh-A: Core Ring without Strap
+    set grid $current_grid
+  }
+  set layers [check_layer_names [dict get $args -layers]]
+
+  set process_args $args
+  while {[llength $process_args] > 0} {
+    set arg [lindex $process_args 0]
+    set value [lindex $process_args 1]
+
+    switch $arg {
+      -grid            {;}
+      -layers          {;}
+      -widths {
+        if {[catch {check_max_length $value 2} msg]} {
+          utl::error PDN 115 "Unexpected number of values for -widths, $msg."
+        }
+        if {[llength $value] == 1} {
+          set values [list $value $value]
+        } else {
+          set values $value
+        }
+        foreach layer $layers width $values {
+          dict set grid core_ring $layer width $width
+        }
+      }
+      -spacings {
+        if {[catch {check_max_length $value 2} msg]} {
+          utl::error PDN 116 "Unexpected number of values for -spacings, $msg."
+        }
+        if {[llength $value] == 1} {
+          set values [list $value $value]
+        } else {
+          set values $value
+        }
+        foreach layer $layers spacing $values {
+          dict set grid core_ring $layer spacing $spacing
+        }
+      }
+      -core_offsets {
+        if {[catch {check_max_length $value 2} msg]} {
+          utl::error PDN 117 "Unexpected number of values for -core_offsets, $msg."
+        }
+        if {[llength $value] == 1} {
+          set values [list $value $value]
+        } else {
+          set values $value
+        }
+        foreach layer $layers offset $values {
+          dict set grid core_ring $layer core_offset $offset
+        }
+      }
+      -pad_offsets {
+        if {[catch {check_max_length $value 2} msg]} {
+          utl::error PDN 118 "Unexpected number of values for -pad_offsets, $msg."
+        }
+        if {[llength $value] == 1} {
+          set values [list $value $value]
+        } else {
+          set values $value
+        }
+        foreach layer $layers offset $values {
+          dict set grid core_ring $layer pad_offset $offset
+        }
+      }
+      -power_pads      {dict set grid pwr_pads [check_pwr_pads $grid $value]}
+      -ground_pads     {dict set grid gnd_pads [check_gnd_pads $grid $value]}
+      default          {utl::error PDN 125 "Unrecognized argument $arg, should be one of -grid, -type, -orient, -power_pins, -ground_pins, -blockages, -rails, -straps, -connect."}
+    }
+
+    set process_args [lrange $process_args 2 end]
+  }
+
+  set current_grid [verify_grid $grid]
+}
+
+proc check_fixed_vias {via_names} {
+  set tech [ord::get_db_tech]
+
+  foreach via_name $via_names {
+    if {[set via [$tech findVia $via_name]] == "NULL"} {
+      utl::error "PDN" 119 "Via $via_name specified in the grid specification does not exist in this technology."
+    }
+  }
+
+  return $via_names
+}
+
+proc add_pdn_connect {args} {
+  variable current_grid
+
+  if {[dict exists $args -grid]} {
+    set current_grid [check_grid [get_grid [dict get $args -grid]]]
+  }
+  set grid $current_grid
+
+  set layers [check_layer_names [dict get $args -layers]]
+
+  set process_args $args
+  while {[llength $process_args] > 0} {
+    set arg [lindex $process_args 0]
+    set value [lindex $process_args 1]
+
+    switch $arg {
+      -grid            {;}
+      -layers          {;}
+      -cut_pitch       {dict set layers constraints cut_pitch $value}
+      -fixed_vias      {dict set layers fixed_vias [check_fixed_vias $value]}
+      default          {utl::error PDN 126 "Unrecognized argument $arg, should be one of -grid, -type, -orient, -power_pins, -ground_pins, -blockages, -rails, -straps, -connect."}
+    }
+
+    set process_args [lrange $process_args 2 end]
+  }
+
+  dict lappend grid connect $layers
+  set current_grid [verify_grid $grid]
+}
+
+proc convert_grid_to_def_units {grid} {
+  if {![dict exists $grid units]} {
+    if {[dict exists $grid core_ring]} {
+      dict for {layer data} [dict get $grid core_ring] {
+        dict set grid core_ring $layer [convert_layer_spec_to_def_units $data]
+      }
+    }
+  
+    if {[dict exists $grid rails]} {
+      dict for {layer data} [dict get $grid rails] {
+        dict set grid rails $layer [convert_layer_spec_to_def_units $data]
+        if {[dict exists $grid template]} {
+          foreach template [dict get $grid template names] {
+            if {[dict exists $grid layers $layer $template]} {
+              dict set grid rails $layer $template [convert_layer_spec_to_def_units [dict get $grid rails $layer $template]]
+            }
+          }
+        }
+      }
+    }
+    if {[dict exists $grid straps]} {
+      dict for {layer data} [dict get $grid straps] {
+        dict set grid straps $layer [convert_layer_spec_to_def_units $data]
+        if {[dict exists $grid template]} {
+          foreach template [dict get $grid template names] {
+            if {[dict exists $grid straps $layer $template]} {
+              dict set grid straps $layer $template [convert_layer_spec_to_def_units [dict get $grid straps $layer $template]]
+            }
+          }
+        }
+      }
+    }
+    dict set grid units "db"
+  }
+
+  return $grid
+}
+
+proc get_inst_pin_connected_to_net {inst net} {
+  foreach iterm [$inst getITerms] {
+    # debug "[$inst getName] [$iterm getNet] == $net"
+    if {[$iterm getNet] == $net} {
+      return [[$iterm getMTerm] getName]
+    }
+  }
+}
+
+proc filter_out_selected_by {instances selection} {
+  dict for {inst_name instance} $instances {
+    if {[dict exists $instance selected_by]} {
+      if {[dict get $instance selected_by] == $selection} {
+        set instances [dict remove $instances $inst_name]
+      }
+    }
+  }
+
+  return $instances
+}
+
+proc get_priority_value {priority} {
+  if {$priority == "inst_name"} {return 4}
+  if {$priority == "cell_name"} {return 3}
+  if {$priority == "orient"} {return 2}
+  if {$priority == "none"} {return 1}
+}
+
+proc set_instance_grid {inst_name grid priority} {
+  variable instances
+
+  # debug "start- inst_name $inst_name, grid: [dict get $grid name], priority: $priority"
+  set grid_name [dict get $grid name]
+  set priority_value [get_priority_value $priority]
+  set instance [dict get $instances $inst_name]
+  if {[dict exists $instance grid]} {
+    if {[dict get $instance grid] != $grid_name} {
+      set current_priority_value [get_priority_value [dict get $instance selected_by]]
+      if {$priority_value < $current_priority_value} {
+        return
+      } elseif {$priority_value == $current_priority_value} {
+        utl::error PDN 165 "Conflict found, instance $inst_name is part of two grid definitions ($grid_name, [dict get $instances $inst_name grid])."
+      }
+    }
+  } else {
+    dict set instances $inst_name grid $grid_name
+  }
+
+  if {[dict exists $grid halo]} {
+    set_instance_halo $inst_name [dict get $grid halo]
+  }
+  dict set instances $inst_name selected_by $priority
+  dict set instances $inst_name grid $grid_name
+  dict set insts $inst_name selected_by $priority
+  dict set insts $inst_name grid $grid_name
+}
+
+proc verify_grid {grid} {
+  variable design_data
+  variable default_grid_data
+
+  if {![dict exists $grid type]} {
+    dict set grid type stdcell
+  }
+  set type [dict get $grid type]
+
+  if {![dict exists $grid voltage_domains]} {
+    dict set grid voltage_domains "CORE"
+  }
+  set voltage_domains [dict get $grid voltage_domains]
+
+  if {![dict exists $grid name]} {
+    set idx 1
+    set name "[join [dict get $grid voltage_domains] {_}]_${type}_grid_$idx"
+    while {[get_grid $name] != {}} {
+      incr idx
+      set name "[join [dict get $grid voltage_domains] {_}]_${type}_grid_$idx"
+    }
+    dict set grid name $name
+  }
+  set grid_name [dict get $grid name]
+
+  if {[dict exists $grid core_ring]} {
+    check_core_ring [dict get $grid core_ring]
+    set layer [lindex [dict keys [dict get $grid core_ring]]]
+    if {[dict exist $grid core_ring $layer pad_offset]} {
+      if {![dict exists $grid pwr_pads]} {
+        utl::error PDN 147 "No definition of power padcells provided, required when using pad_offset."
+      }
+      if {![dict exists $grid gnd_pads]} {
+        utl::error PDN 148 "No definition of ground padcells provided, required when using pad_offset."
+      }
+    }
+  }
+
+  if {[dict exists $grid pwr_pads]} {
+    dict for {pin_name cells} [dict get $grid pwr_pads] {
+      foreach cell $cells {
+        if {[set master [[ord::get_db] findMaster $cell]] == "NULL"} {
+          utl::error PDN 153  "Core power padcell ($cell) not found in the database."
+        } 
+        if {[$master findMTerm $pin_name] == "NULL"} {
+          utl::error PDN 154 "Cannot find pin ($pin_name) on core power padcell ($cell)."
+        }
+      } 
+    }
+  }
+
+  if {[dict exists $grid gnd_pads]} {
+    dict for {pin_name cells} [dict get $grid gnd_pads] {
+      foreach cell $cells {
+        if {[set master [[ord::get_db] findMaster $cell]] == "NULL"} {
+          utl::error PDN 155  "Core ground padcell ($cell) not found in the database."
+        } 
+        if {[$master findMTerm $pin_name] == "NULL"} {
+          utl::error PDN 156 "Cannot find pin ($pin_name) on core ground padcell ($cell)."
+        }
+      } 
+    }
+  }
+
+  if {[dict exists $grid macro]} {
+    check_cells [dict get $grid macro]
+  }
+ 
+  if {[dict exists $grid rails]} {
+    dict set grid rails [check_rails [dict get $grid rails]]
+  }
+
+  if {[dict exists $grid straps]} {
+    check_straps [dict get $grid straps]
+  }
+
+  if {[dict exists $grid template]} {
+    set_template_size {*}[dict get $grid template size]
+  }
+  
+  if {[dict exists $grid orient]} {
+    if {$type == "stdcell"} {
+      utl::error PDN 90 "The orient attribute cannot be used with stdcell grids."
+    }
+    dict set grid orient [check_orientations [dict get $grid orient]]
+  }
+
+  if {[dict exists $grid connect]} {
+    dict set grid connect [check_connect $grid [dict get $grid connect]]
+  }
+
+  if {$type == "macro"} {
+    if {![dict exists $grid halo]} {
+      dict set grid halo [get_default_halo]
+    }
+    check_halo [dict get $grid halo]
+  } else {
+    set default_grid_data $grid
+  }
+
+  # debug $grid
+
+  dict set design_data grid $type $grid_name $grid
+  return $grid
+}
+
+proc complete_macro_grid_specifications {} {
+  variable design_data
+  variable instances
+  variable macros
+
+  set macros [get_macro_blocks]
+
+  dict for {type grid_types} [dict get $design_data grid] {
+    dict for {name grid} $grid_types {
+      dict set design_data grid $type $name [convert_grid_to_def_units $grid]
+    }
+  }
+  if {![dict exists $design_data grid macro]} {
+    return
+  }
+
+  ########################################
+  # Creating blockages based on macro locations
+  #######################################
+  # debug "import_macro_boundaries"
+  import_macro_boundaries
+
+  # Associate each block instance with a grid specification
+  set macro_names [dict keys $macros]
+  dict for {grid_name grid} [dict get $design_data grid macro] {
+    set insts [find_instances_of $macro_names]
+    set boundary [odb::newSetFromRect {*}[get_core_area]]
+    set insts [filtered_insts_within $insts $boundary]
+    if {[dict exists $grid instances]} {
+      # debug "Check macro name for [dict get $grid name]"
+      dict for {inst_name instance} $insts {
+        if {[lsearch [dict get $grid instances] $inst_name] > -1} {
+          set_instance_grid $inst_name $grid inst_name
+	}
+      }
+      set insts [set_instance_grid $selected_insts $grid inst_name]
+    } elseif {[dict exists $grid macro]} {
+      # set insts [filter_out_selected_by $insts inst_name]
+      # debug "Check instance name for [dict get $grid name]"
+      dict for {inst_name instance} $insts {
+        set cell_name [dict get $instance macro]
+        if {[lsearch [dict get $grid macro] $cell_name] > -1} {
+          set_instance_grid $inst_name $grid cell_name
+        }
+      }
+    } elseif {[dict exists $grid orient]} {
+      # set insts [filter_out_selected_by $insts inst_name]
+      # set insts [filter_out_selected_by $insts cell_name]
+      # debug "Check orientation for [dict get $grid name]"
+      dict for {inst_name instance} $insts {
+        set orient [dict get $instance orient]
+	# debug "Inst: $inst_name, orient: $orient, compare to: [dict get $grid orient]"
+        if {[lsearch [dict get $grid orient] $orient] > -1} {
+          set_instance_grid $inst_name $grid orient
+        }
+      }
+    }
+  }
+  dict for {grid_name grid} [dict get $design_data grid macro] {
+    set related_instances {}
+    dict for {inst instance} $instances {
+      if {![dict exists $instance grid]} {
+        # utl::error PDN 166 "Instance $inst of cell [dict get $instance macro] is not associated with any grid."
+        dict set instance grid "__none__"
+      }
+      if {[dict get $instance grid] == $grid_name} {
+        dict set related_instances $inst $instance 
+      }
+    }
+    dict set design_data grid macro $grid_name _related_instances $related_instances
+  }
+
+  dict for {grid_name grid} [dict get $design_data grid macro] {
+    # Set the pin layer on the connect statement to the pin layer of the def to be _PIN_<dir>
+    set blockages {}
+    set pin_layers {}
+    set power_pins {}
+    set ground_pins {}
+    dict for {instance_name instance} [dict get $grid _related_instances] {
+      lappend blockages {*}[dict get $macros [dict get $instance macro] blockage_layers]
+      lappend pin_layers {*}[dict get $macros [dict get $instance macro] pin_layers]
+      lappend power_pins {*}[dict get $macros [dict get $instance macro] power_pins]
+      lappend ground_pins {*}[dict get $macros [dict get $instance macro] ground_pins]
+    }
+    dict set design_data grid macro $grid_name power_pins [lsort -unique $power_pins]
+    dict set design_data grid macro $grid_name ground_pins [lsort -unique $ground_pins]
+
+    if {[dict exists $grid pin_direction]} {
+      if {[dict get $grid pin_direction] == "vertical"} {
+        set direction ver
+      } else {
+        set direction hor
+      }
+      set pin_layers [lsort -unique $pin_layers]
+
+      foreach pin_layer $pin_layers {
+        set new_connections {}
+        foreach connect [dict get $grid connect] {
+          if {[lindex $connect 0] == $pin_layer} {
+            set connect [lreplace $connect 0 0 ${pin_layer}_PIN_$direction]
+          }
+          if {[lindex $connect 1] == $pin_layer} {
+            set connect [lreplace $connect 1 1 ${pin_layer}_PIN_$direction]
+          }
+          lappend new_connections $connect
+        }
+        dict set design_data grid macro $grid_name connect $new_connections
+      }
+    }
+    
+    if {[dict exists $grid straps]} {
+      foreach strap_layer [dict keys [dict get $grid straps]] {
+        lappend blockages $strap_layer
+      }
+    }
+    # debug "Grid: $grid_name"
+    # debug "  instances: [dict keys [dict get $grid _related_instances]]"
+    # debug "  blockages: [lsort -unique $blockages]"
+    # debug "  connect: [dict get $design_data grid macro $grid_name connect]"
+
+    dict set design_data grid macro $grid_name blockages [lsort -unique $blockages]
+  }
+
+ # debug "get_memory_instance_pg_pins"
+  get_memory_instance_pg_pins
+}
+
+#This file contains procedures that are used for PDN generation
+proc debug {message} {
+  set state [info frame -1]
+  set str ""
+  if {[dict exists $state file]} {
+    set str "$str[dict get $state file]:"
+  }
+  if {[dict exists $state proc]} {
+    set str "$str[dict get $state proc]:"
+  }
+  if {[dict exists $state line]} {
+    set str "$str[dict get $state line]"
+  }
+  puts "\[DEBUG\] $str: $message"
+}
+
+proc lmap {args} {
+  set result {}
+  set var [lindex $args 0]
+  foreach item [lindex $args 1] {
+    uplevel 1 "set $var $item"
+    lappend result [uplevel 1 [lindex $args end]]
+  }
+  return $result
+}
+
+proc get_routing_direction {layer_name} {
+  variable layers
+
+  if {$layers == ""} {
+    init_metal_layers
+  }
+
+  if {![dict exists $layers $layer_name direction]} {
+    utl::error "PDN" 33 "Unknown direction for layer $layer_name."
+  }
+  return [dict get $layers $layer_name direction]
+}
+
+proc get_dir {layer_name} {
+  if {[regexp {.*_PIN_(hor|ver)} $layer_name - dir]} {
+    return $dir
+  }
+
+  if {[is_rails_layer $layer_name]} {
+    return "hor"
+  }
+
+  return [get_routing_direction $layer_name]
+}
+
+proc get_rails_layers {} {
+  variable design_data
+
+  if {[dict exists $design_data grid]} {
+    foreach type [dict keys [dict get $design_data grid]] {
+      dict for {name specification} [dict get $design_data grid $type] {
+        if {[dict exists $specification rails]} {
+          return [dict keys [dict get $specification rails]]
+        }
+      }
+    }
+  }
+  return {}
+}
+
+proc is_rails_layer {layer} {
+  return [expr {[lsearch -exact [get_rails_layers] $layer] > -1}]
+}
+
+proc via_number {layer_rule1 layer_rule2} {
+  return [expr [[$layer_rule1 getLayer] getNumber] - [[$layer_rule2 getLayer] getNumber]]
+}
+
+proc init_via_tech {} {
+  variable tech
+  variable def_via_tech
+
+  set def_via_tech {}
+  foreach via_rule [$tech getViaGenerateRules] {
+    set levels [list [$via_rule getViaLayerRule 0] [$via_rule getViaLayerRule 1] [$via_rule getViaLayerRule 2]]
+    set levels [lsort -command via_number $levels]
+    lassign $levels lower cut upper
+
+    dict set def_via_tech [$via_rule getName] [list \
+      lower [list layer [[$lower getLayer] getName] enclosure [$lower getEnclosure]] \
+      upper [list layer [[$upper getLayer] getName] enclosure [$upper getEnclosure]] \
+      cut   [list layer [[$cut getLayer] getName] spacing [$cut getSpacing] size [list [[$cut getRect] dx] [[$cut getRect] dy]]] \
+    ]
+  }
+  # debug "def_via_tech: $def_via_tech"
+}
+
+proc set_prop_lines {obj prop_name} {
+  variable prop_line
+  if {[set prop [::odb::dbStringProperty_find $obj $prop_name]] != "NULL"} {
+    set prop_line [$prop getValue]
+  } else {
+    set prop_line {}
+  }
+}
+
+proc read_propline {} {
+  variable prop_line
+
+  set word [lindex $prop_line 0]
+  set prop_line [lrange $prop_line 1 end]
+
+  set line {}
+  while {[llength $prop_line] > 0 && $word != ";"} {
+    lappend line $word
+    set word [lindex $prop_line 0]
+    set prop_line [lrange $prop_line 1 end]
+  }
+  return $line
+}
+
+proc empty_propline {} {
+  variable prop_line
+  return [expr ![llength $prop_line]]
+}
+
+proc find_layer {layer_name} {
+  variable tech
+
+  if {[set layer [$tech findLayer $layer_name]] == "NULL"} {
+    utl::error "PDN" 19 "Cannot find layer $layer_name in loaded technology."
+  }
+  return $layer
+}
+
+proc read_spacing {layer_name} {
+  variable layers
+  variable def_units
+
+  set layer [find_layer $layer_name]
+
+  set_prop_lines $layer LEF58_SPACING
+  set spacing {}
+
+  while {![empty_propline]} {
+    set line [read_propline]
+    if {[set idx [lsearch -exact $line CUTCLASS]] > -1} {
+      set cutclass [lindex $line [expr $idx + 1]]
+      set line [lreplace $line $idx [expr $idx + 1]]
+
+      if {[set idx [lsearch -exact $line LAYER]] > -1} {
+        set other_layer [lindex $line [expr $idx + 1]]
+        set line [lreplace $line $idx [expr $idx + 1]]
+
+        if {[set idx [lsearch -exact $line CONCAVECORNER]] > -1} {
+          set line [lreplace $line $idx $idx]
+
+          if {[set idx [lsearch -exact $line SPACING]] > -1} {
+            dict set spacing $cutclass $other_layer concave [expr round([lindex $line [expr $idx + 1]] * $def_units)]
+            # set line [lreplace $line $idx [expr $idx + 1]]
+          }
+        }
+      }
+    }
+  }
+  # debug "$layer_name $spacing"
+  dict set layers $layer_name spacing $spacing
+  # debug "$layer_name [dict get $layers $layer_name]"
+}
+
+proc read_spacingtables {layer_name} {
+  variable layers
+  variable def_units
+
+  set layer [find_layer $layer_name]
+  set prls {}
+
+  if {[$layer hasTwoWidthsSpacingRules]} {
+    set type "TWOWIDTHS"
+    set subtype "NONE"
+
+    set table_size [$layer getTwoWidthsSpacingTableNumWidths]
+    for {set i 0} {$i < $table_size} {incr i} {
+      set width [$layer getTwoWidthsSpacingTableWidth $i]
+
+      if {[$layer getTwoWidthsSpacingTableHasPRL $i]} {
+        set prl [$layer getTwoWidthsSpacingTablePRL $i]
+      } else {
+        set prl 0
+      }
+      set spacings {}
+      for {set j 0} {$j < $table_size} {incr j} {
+        lappend spacings [$layer getTwoWidthsSpacingTableEntry $i $j]
+      }
+
+      dict set layers $layer_name spacingtable $type $subtype $width [list prl $prl spacings $spacings]
+    }
+  }
+
+  set_prop_lines $layer LEF58_SPACINGTABLE
+  set spacing {}
+
+  while {![empty_propline]} {
+    set line [read_propline]
+    # debug "$line"
+    set type [lindex $line 1]
+    set subtype [lindex $line 2]
+
+    set table_entry_indexes [lsearch -exact -all $line "WIDTH"]
+    set num_entries [llength $table_entry_indexes]
+
+    foreach start_index $table_entry_indexes {
+      set pos $start_index
+      incr pos
+      set width [expr round([lindex $line $pos] * $def_units)]
+      incr pos
+      if {[lindex $line $pos] == "PRL"} {
+        incr pos
+        set prl [expr round([lindex $line $pos] * $def_units)]
+        incr pos
+      } else {
+        set prl 0
+      }
+      set spacings {}
+      for {set i 0} {$i < $num_entries} {incr i} {
+        # debug "[expr $i + $pos] [lindex $line [expr $i + $pos]]"
+        lappend spacings [expr round([lindex $line [expr $i + $pos]] * $def_units)]
+      }
+      dict set layers $layer_name spacingtable $type $subtype $width [list prl $prl spacings $spacings]
+    }
+  }
+
+  if {![dict exists $layers $layer_name spacingtable]} {
+    dict set layers $layer_name spacingtable {}
+  }
+  # debug "$layer_name [dict get $layers $layer_name]"
+}
+
+proc get_spacingtables {layer_name} {
+  variable layers
+
+  if {![dict exists $layers $layer_name spacingtable]} {
+    read_spacingtables $layer_name
+  }
+
+  return [dict get $layers $layer_name spacingtable]
+}
+
+proc get_concave_spacing_value {layer_name other_layer_name} {
+  variable layers
+  variable default_cutclass
+
+  if {![dict exists $layers $layer_name spacing]} {
+    read_spacing $layer_name
+  }
+  # debug "$layer_name [dict get $layers $layer_name]"
+  if {[dict exists $layers $layer_name spacing [dict get $default_cutclass $layer_name] $other_layer_name concave]} {
+    return [dict get $layers $layer_name spacing [dict get $default_cutclass $layer_name] $other_layer_name concave]
+  }
+  return 0
+}
+
+proc read_arrayspacing {layer_name} {
+  variable layers
+  variable def_units
+
+  set layer [find_layer $layer_name]
+
+  set_prop_lines $layer LEF58_ARRAYSPACING
+  set arrayspacing {}
+
+  while {![empty_propline]} {
+    set line [read_propline]
+    if {[set idx [lsearch -exact $line PARALLELOVERLAP]] > -1} {
+      dict set arrayspacing paralleloverlap 1
+      set line [lreplace $line $idx $idx]
+    }
+    if {[set idx [lsearch -exact $line LONGARRAY]] > -1} {
+      dict set arrayspacing longarray 1
+      set line [lreplace $line $idx $idx]
+    }
+    if {[set idx [lsearch -exact $line CUTSPACING]] > -1} {
+      dict set arrayspacing cutspacing [expr round([lindex $line [expr $idx + 1]] * $def_units)]
+      set line [lreplace $line $idx [expr $idx + 1]]
+    }
+    while {[set idx [lsearch -exact $line ARRAYCUTS]] > -1} {
+      dict set arrayspacing arraycuts [lindex $line [expr $idx + 1]] spacing [expr round([lindex $line [expr $idx + 3]] * $def_units)]
+      set line [lreplace $line $idx [expr $idx + 3]]
+    }
+  }
+  dict set layers $layer_name arrayspacing $arrayspacing
+}
+
+proc read_cutclass {layer_name} {
+  variable layers
+  variable def_units
+  variable default_cutclass
+
+  set layer [find_layer $layer_name]
+  set_prop_lines $layer LEF58_CUTCLASS
+  dict set layers $layer_name cutclass {}
+  set min_area -1
+
+  while {![empty_propline]} {
+    set line [read_propline]
+    if {![regexp {CUTCLASS\s+([^\s]+)\s+WIDTH\s+([^\s]+)} $line - cut_class width]} {
+      utl::error "PDN" 20 "Failed to read CUTCLASS property '$line'."
+    }
+    if {[regexp {LENGTH\s+([^\s]+)} $line - length]} {
+      set area [expr $width * $length]
+    } else {
+      set area [expr $width * $width]
+    }
+    if {$min_area == -1 || $area < $min_area} {
+      dict set default_cutclass $layer_name $cut_class
+      set min_area $area
+    }
+    dict set layers $layer_name cutclass $cut_class [list width [expr round($width * $def_units)] length [expr round($length * $def_units)]]
+  }
+}
+
+proc read_enclosures {layer_name} {
+  variable layers
+  variable def_units
+
+  set layer [find_layer $layer_name]
+  set_prop_lines $layer LEF58_ENCLOSURE
+  set prev_cutclass ""
+
+  while {![empty_propline]} {
+    set line [read_propline]
+    # debug "$line"
+    set enclosure {}
+    if {[set idx [lsearch -exact $line EOL]] > -1} {
+      continue
+      dict set enclosure eol [expr round([lindex $line [expr $idx + 1]] * $def_units)]
+      set line [lreplace $line $idx [expr $idx + 1]]
+    }
+    if {[set idx [lsearch -exact $line EOLONLY]] > -1} {
+      dict set enclosure eolonly 1
+      set line [lreplace $line $idx $idx]
+    }
+    if {[set idx [lsearch -exact $line SHORTEDGEONEOL]] > -1} {
+      dict set enclosure shortedgeoneol 1
+      set line [lreplace $line $idx $idx]
+    }
+    if {[set idx [lsearch -exact $line MINLENGTH]] > -1} {
+      dict set enclosure minlength [expr round([lindex $line [expr $idx + 1]] * $def_units)]
+      set line [lreplace $line $idx [expr $idx + 1]]
+    }
+    if {[set idx [lsearch -exact $line ABOVE]] > -1} {
+      dict set enclosure above 1
+      set line [lreplace $line $idx $idx]
+    }
+    if {[set idx [lsearch -exact $line BELOW]] > -1} {
+      dict set enclosure below 1
+      set line [lreplace $line $idx $idx]
+    }
+    if {[set idx [lsearch -exact $line END]] > -1} {
+      dict set enclosure end 1
+      set line [lreplace $line $idx $idx]
+    }
+    if {[set idx [lsearch -exact $line SIDE]] > -1} {
+      dict set enclosure side 1
+      set line [lreplace $line $idx $idx]
+    }
+
+    set width 0
+    regexp {WIDTH\s+([^\s]+)} $line - width
+    set width [expr round($width * $def_units)]
+
+    if {![regexp {ENCLOSURE CUTCLASS\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)} $line - cut_class overlap1 overlap2]} {
+      utl::error "PDN" 21 "Failed to read ENCLOSURE property '$line'."
+    }
+    dict set enclosure overlap1 [expr round($overlap1 * $def_units)]
+    dict set enclosure overlap2 [expr round($overlap2 * $def_units)]
+    # debug "class - $cut_class enclosure - $enclosure"
+    if {$prev_cutclass != $cut_class} {
+      set enclosures {}
+      set prev_cutclass $cut_class
+    }
+    dict lappend enclosures $width $enclosure
+    dict set layers $layer_name cutclass $cut_class enclosures $enclosures
+  }
+  # debug "end"
+}
+
+proc read_minimumcuts {layer_name} {
+  variable layers
+  variable def_units
+  variable default_cutclass
+
+  set layer [find_layer $layer_name]
+  set_prop_lines $layer LEF58_MINIMUMCUT
+
+  while {![empty_propline]} {
+    set line [read_propline]
+    set classes {}
+    set constraints {}
+    set fromabove 0
+    set frombelow 0
+
+    if {[set idx [lsearch -exact $line FROMABOVE]] > -1} {
+      set fromabove 1
+      set line [lreplace $line $idx $idx]
+    } elseif {[set idx [lsearch -exact $line FROMBELOW]] > -1} {
+      set frombelow 1
+      set line [lreplace $line $idx $idx]
+    } else {
+      set fromabove 1
+      set frombelow 1
+    }
+
+    if {[set idx [lsearch -exact $line WIDTH]] > -1} {
+      set width [expr round([lindex $line [expr $idx + 1]] * $def_units)]
+    }
+
+    if {[regexp {LENGTH ([0-9\.]*) WITHIN ([0-9\.]*)} $line - length within]} {
+      # Not expecting to deal with this king of structure, so can ignore
+      set line [regsub {LENGTH ([0-9\.]*) WITHIN ([0-9\.]*)} $line {}]
+    }
+
+    if {[regexp {AREA ([0-9\.]*) WITHIN ([0-9\.]*)} $line - area within]} {
+      # Not expecting to deal with this king of structure, so can ignore
+      set line [regsub {AREA ([0-9\.]*) WITHIN ([0-9\.]*)} $line {}]
+    }
+
+    while {[set idx [lsearch -exact $line CUTCLASS]] > -1} {
+      set cutclass [lindex $line [expr $idx + 1]]
+      set num_cuts [lindex $line [expr $idx + 2]]
+
+      if {$fromabove == 1} {
+        dict set layers $layer_name minimumcut width $width fromabove $cutclass $num_cuts
+      }
+      if {$frombelow == 1} {
+        dict set layers $layer_name minimumcut width $width frombelow $cutclass $num_cuts
+      }
+
+      set line [lreplace $line $idx [expr $idx + 2]]
+    }
+  }
+}
+
+proc get_minimumcuts {layer_name width from cutclass} {
+  variable layers
+  # debug "$layer_name, $width, $from, $cutclass"
+  if {![dict exists $layers $layer_name minimumcut]} {
+    read_minimumcuts $layer_name
+    # debug "[dict get $layers $layer_name minimumcut]"
+  }
+
+  set min_cuts 1
+
+  if {![dict exists $layers $layer_name minimumcut]} {
+    # debug "No mincut rule for layer $layer_name"
+    return $min_cuts
+  }
+
+  set idx 0
+  set widths [lsort -integer -decreasing [dict keys [dict get $layers $layer_name minimumcut width]]]
+  if {$width <= [lindex $widths end]} {
+    # debug "width $width less than smallest width boundary [lindex $widths end]"
+    return $min_cuts
+  }
+  foreach width_boundary [lreverse $widths] {
+    if {$width > $width_boundary && [dict exists $layers $layer_name minimumcut width $width_boundary $from]} {
+      # debug "[dict get $layers $layer_name minimumcut width $width_boundary]"
+      if {[dict exists $layers $layer_name minimumcut width $width_boundary $from $cutclass]} {
+        set min_cuts [dict get $layers $layer_name minimumcut width $width_boundary $from $cutclass]
+      }
+      # debug "Selected width boundary $width_boundary for $layer_name, $width $from, $cutclass [dict get $layers $layer_name minimumcut width $width_boundary $from $cutclass]"
+      break
+    }
+  }
+
+  return $min_cuts
+}
+
+proc get_via_enclosure {via_info lower_width upper_width} {
+  variable layers
+  variable default_cutclass
+  variable min_lower_enclosure
+  variable max_lower_enclosure
+  variable min_upper_enclosure
+  variable max_upper_enclosure
+
+  # debug "via_info $via_info width $lower_width,$upper_width"
+  set layer_name [dict get $via_info cut layer]
+
+  if {![dict exists $layers $layer_name cutclass]} {
+    read_cutclass $layer_name
+    read_enclosures $layer_name
+  }
+
+  if {!([dict exists $default_cutclass $layer_name] && [dict exists $layers $layer_name cutclass [dict get $default_cutclass $layer_name] enclosures])} {
+    set lower_enclosure [dict get $via_info lower enclosure]
+    set upper_enclosure [dict get $via_info upper enclosure]
+
+    set min_lower_enclosure [lindex $lower_enclosure 0]
+    set max_lower_enclosure [lindex $lower_enclosure 1]
+
+    if {$max_lower_enclosure < $min_lower_enclosure} {
+      set swap $min_lower_enclosure
+      set min_lower_enclosure $max_lower_enclosure
+      set max_lower_enclosure $swap
+    }
+
+    set min_upper_enclosure [lindex $upper_enclosure 0]
+    set max_upper_enclosure [lindex $upper_enclosure 1]
+
+    if {$max_upper_enclosure < $min_upper_enclosure} {
+      set swap $min_upper_enclosure
+      set min_upper_enclosure $max_upper_enclosure
+      set max_upper_enclosure $swap
+    }
+
+    set selected_enclosure [list $min_lower_enclosure $max_lower_enclosure $min_upper_enclosure $max_upper_enclosure]
+  } else {
+    set enclosures [dict get $layers $layer_name cutclass [dict get $default_cutclass $layer_name] enclosures]
+    # debug "Enclosure set $enclosures"
+    set upper_enclosures {}
+    set lower_enclosures {}
+
+    set width $lower_width
+
+    foreach size [lreverse [dict keys $enclosures]] {
+      if {$width >= $size} {
+          break
+      }
+    }
+
+    set enclosure_list [dict get $enclosures $size]
+    # debug "Initial enclosure_list (size = $size)- $enclosure_list"
+    if {$size > 0} {
+      foreach enclosure $enclosure_list {
+        if {![dict exists $enclosure above]} {
+          lappend lower_enclosures $enclosure
+        }
+      }
+    }
+
+    set width $upper_width
+
+    foreach size [lreverse [dict keys $enclosures]] {
+      if {$width >= $size} {
+          break
+      }
+    }
+
+    set enclosure_list [dict get $enclosures $size]
+    # debug "Initial enclosure_list (size = $size)- $enclosure_list"
+    if {$size > 0} {
+      foreach enclosure $enclosure_list {
+        if {![dict exists $enclosure below]} {
+          lappend upper_enclosures $enclosure
+        }
+      }
+    }
+
+    if {[llength $upper_enclosures] == 0} {
+      set zero_enclosures_list [dict get $enclosures 0]
+      foreach enclosure $zero_enclosures_list {
+        if {![dict exists $enclosure below]} {
+          lappend upper_enclosures $enclosure
+        }
+      }
+    }
+    if {[llength $lower_enclosures] == 0} {
+      set zero_enclosures_list [dict get $enclosures 0]
+      foreach enclosure $zero_enclosures_list {
+        if {![dict exists $enclosure above]} {
+          lappend lower_enclosures $enclosure
+        }
+      }
+    }
+    set upper_min -1
+    set lower_min -1
+    if {[llength $upper_enclosures] > 1} {
+      foreach enclosure $upper_enclosures {
+        # debug "upper enclosure - $enclosure"
+        set this_min [expr min([dict get $enclosure overlap1], [dict get $enclosure overlap2])]
+        if {$upper_min < 0 || $this_min < $upper_min} {
+          set upper_min $this_min
+          set upper_enc [list [dict get $enclosure overlap1] [dict get $enclosure overlap2]]
+          # debug "upper_enc: $upper_enc"
+        }
+      }
+    } else {
+      set enclosure [lindex $upper_enclosures 0]
+      set upper_enc [list [dict get $enclosure overlap1] [dict get $enclosure overlap2]]
+    }
+    if {[llength $lower_enclosures] > 1} {
+      foreach enclosure $lower_enclosures {
+        # debug "lower enclosure - $enclosure"
+        set this_min [expr min([dict get $enclosure overlap1], [dict get $enclosure overlap2])]
+        if {$lower_min < 0 || $this_min < $lower_min} {
+          set lower_min $this_min
+          set lower_enc [list [dict get $enclosure overlap1] [dict get $enclosure overlap2]]
+        }
+      }
+      # debug "[llength $lower_enclosures] lower_enc: $lower_enc"
+    } else {
+      set enclosure [lindex $lower_enclosures 0]
+      set lower_enc [list [dict get $enclosure overlap1] [dict get $enclosure overlap2]]
+      # debug "1 lower_enc: lower_enc: $lower_enc"
+    }
+    set selected_enclosure [list {*}$lower_enc {*}$upper_enc]
+  }
+  # debug "selected $selected_enclosure"
+  set min_lower_enclosure [expr min([lindex $selected_enclosure 0], [lindex $selected_enclosure 1])]
+  set max_lower_enclosure [expr max([lindex $selected_enclosure 0], [lindex $selected_enclosure 1])]
+  set min_upper_enclosure [expr min([lindex $selected_enclosure 2], [lindex $selected_enclosure 3])]
+  set max_upper_enclosure [expr max([lindex $selected_enclosure 2], [lindex $selected_enclosure 3])]
+  # debug "enclosures - min_lower $min_lower_enclosure max_lower $max_lower_enclosure min_upper $min_upper_enclosure max_upper $max_upper_enclosure"
+}
+
+proc select_via_info {lower} {
+  variable def_via_tech
+
+  set layer_name $lower
+  regexp {(.*)_PIN} $lower - layer_name
+
+  return [dict filter $def_via_tech script {rule_name rule} {expr {[dict get $rule lower layer] == $layer_name}}]
+}
+
+proc set_layer_info {layer_info} {
+  variable layers
+
+  set layers $layer_info
+}
+
+proc read_widthtable {layer_name} {
+  variable tech
+  variable def_units
+
+  set table {}
+  set layer [find_layer $layer_name]
+  set_prop_lines $layer LEF58_WIDTHTABLE
+
+  while {![empty_propline]} {
+    set line [read_propline]
+    set flags {}
+    if {[set idx [lsearch -exact $line ORTHOGONAL]] > -1} {
+      dict set flags orthogonal 1
+      set line [lreplace $line $idx $idx]
+    }
+    if {[set idx [lsearch -exact $line WRONGDIRECTION]] > -1} {
+      dict set flags wrongdirection 1
+      set line [lreplace $line $idx $idx]
+    }
+
+    regexp {WIDTHTABLE\s+(.*)} $line - widthtable
+    set widthtable [lmap x $widthtable {ord::microns_to_dbu $x}]
+
+    if {[dict exists $flags wrongdirection]} {
+      dict set table wrongdirection $widthtable
+    } else {
+      dict set table rightdirection $widthtable
+    }
+  }
+  return $table
+}
+
+proc get_widthtable {layer_name direction} {
+  variable layers
+
+  if {![dict exists $layers $layer_name widthtable]} {
+      dict set layers $layer_name widthtable [read_widthtable $layer_name]
+  }
+
+  if {![dict exists $layers $layer_name widthtable $direction]} {
+    if {$direction == "wrongdirection" && [dict exists $layers $layer_name widthtable rightdirection]} {
+      dict set layers $layer_name widthtable $direction [dict get $layers $layer_name widthtable rightdirection]
+    } else {
+      dict set layers $layer_name widthtable $direction {}
+    }
+  }
+
+  return [dict get $layers $layer_name widthtable $direction]
+}
+
+# Layers that have a widthtable will only support some width values, the widthtable defines the
+# set of widths that are allowed, or any width greater than or equal to the last value in the
+# table
+#
+
+proc adjust_width {widthtable width} {
+  if {[llength $widthtable] == 0} {return $width}
+  if {[lsearch -exact $widthtable $width] > -1} {return $width}
+  if {$width > [lindex $widthtable end]} {return $width}
+
+  foreach value $widthtable {
+    if {$value > $width} {
+      # debug "Adjust width from $width to $value"
+      return $value
+    }
+  }
+
+  return $width
+}
+
+proc get_adjusted_dX {layer width} {
+  if {[get_routing_direction $layer] == "ver"} {
+    # debug "Using rightdirection adjustment for layer $layer (dX)"
+    return [adjust_width [get_widthtable $layer rightdirection] $width]
+  } else {
+    # debug "Using wrongdirection adjustment for layer $layer (dX)"
+    return [adjust_width [get_widthtable $layer wrongdirection] $width]
+  }
+}
+
+proc get_adjusted_dY {layer height} {
+  if {[get_routing_direction $layer] == "hor"} {
+    # debug "Using rightdirection adjustment for layer $layer (dY)"
+    return [adjust_width [get_widthtable $layer rightdirection] $height]
+  } else {
+    # debug "Using wrongdirection adjustment for layer $layer (dY)"
+    return [adjust_width [get_widthtable $layer wrongdirection] $height]
+  }
+}
+
+proc get_arrayspacing_rule {layer_name} {
+  variable layers
+
+  if {![dict exists $layers $layer_name arrayspacing]} {
+    read_arrayspacing $layer_name
+  }
+
+  return [dict get $layers $layer_name arrayspacing]
+}
+
+proc use_arrayspacing {layer_name rows columns} {
+  set arrayspacing [get_arrayspacing_rule $layer_name]
+  # debug "$arrayspacing"
+  # debug "$rows $columns"
+  if {[llength $arrayspacing] == 0} {
+    # debug "No array spacing rule defined"
+    return 0
+  }
+  # debug "[dict keys [dict get $arrayspacing arraycuts]]"
+  if {[dict exists $arrayspacing arraycuts [expr min($rows,$columns)]]} {
+    # debug "Matching entry in arrayspacing"
+    return 1
+  }
+  if {min($rows,$columns) < [lindex [dict keys [dict get $arrayspacing arraycuts]] 0]} {
+    # debug "row/columns less than min array spacing"
+    return 0
+  }
+  if {min($rows,$columns) > [lindex [dict keys [dict get $arrayspacing arraycuts]] end]} {
+    # debug "row/columns greater than min array spacing"
+    return 1
+  }
+  # debug "default 1"
+  return 1
+}
+
+proc determine_num_via_columns {via_info constraints} {
+  variable upper_width
+  variable lower_width
+  variable upper_height
+  variable lower_height
+  variable lower_dir
+  variable upper_dir
+  variable min_lower_enclosure
+  variable max_lower_enclosure
+  variable min_upper_enclosure
+  variable max_upper_enclosure
+  variable cut_width
+  variable xcut_pitch
+  variable xcut_spacing
+  variable def_units
+
+  # What are the maximum number of columns that we can fit in this space?
+  set i 1
+  if {$lower_dir == "hor"} {
+    set via_width_lower [expr $cut_width + $xcut_pitch * ($i - 1) + 2 * $min_lower_enclosure]
+    set via_width_upper [expr $cut_width + $xcut_pitch * ($i - 1) + 2 * $max_upper_enclosure]
+  } else {
+    set via_width_lower [expr $cut_width + $xcut_pitch * ($i - 1) + 2 * $max_lower_enclosure]
+    set via_width_upper [expr $cut_width + $xcut_pitch * ($i - 1) + 2 * $min_upper_enclosure]
+  }
+  if {[dict exists $constraints cut_pitch]} {set xcut_pitch [expr round([dict get $constraints cut_pitch] * $def_units)]}
+
+  while {$via_width_lower <= $lower_width && $via_width_upper <= $upper_width} {
+    incr i
+    if {$lower_dir == "hor"} {
+      set via_width_lower [expr $cut_width + $xcut_pitch * ($i - 1) + 2 * $max_lower_enclosure]
+      set via_width_upper [expr $cut_width + $xcut_pitch * ($i - 1) + 2 * $min_upper_enclosure]
+    } else {
+      set via_width_lower [expr $cut_width + $xcut_pitch * ($i - 1) + 2 * $min_lower_enclosure]
+      set via_width_upper [expr $cut_width + $xcut_pitch * ($i - 1) + 2 * $max_upper_enclosure]
+    }
+  }
+  set xcut_spacing [expr $xcut_pitch - $cut_width]
+  set columns [expr max(1, $i - 1)]
+  # debug "cols $columns W: via_width_lower $via_width_lower >= lower_width $lower_width || via_width_upper $via_width_upper >= upper_width $upper_width"
+  if {[dict exists $constraints max_columns]} {
+    if {$columns > [dict get $constraints max_columns]} {
+      set columns [dict get $constraints max_columns]
+
+      set lower_concave_enclosure [get_concave_spacing_value [dict get $via_info cut layer] [dict get $via_info lower layer]]
+      # debug "$lower_concave_enclosure $max_lower_enclosure"
+      if {$lower_concave_enclosure > $max_lower_enclosure} {
+        set max_lower_enclosure $lower_concave_enclosure
+      }
+      set upper_concave_enclosure [get_concave_spacing_value [dict get $via_info cut layer] [dict get $via_info upper layer]]
+      # debug "$upper_concave_enclosure $max_upper_enclosure"
+      if {$upper_concave_enclosure > $max_upper_enclosure} {
+        set max_upper_enclosure $upper_concave_enclosure
+      }
+    }
+
+  }
+  # debug "Lower: [dict get $via_info lower layer] $lower_dir"
+  # debug "Upper: [dict get $via_info upper layer] $upper_dir"
+  if {[get_routing_direction [dict get $via_info upper layer]] == "ver"} {
+    if {[dict get $constraints stack_top] != [dict get $via_info upper layer]} {
+      # debug "Adjust width of [dict get $via_info upper layer]"
+      get_via_enclosure $via_info [expr min($lower_width,$lower_height)] [expr min([expr $cut_width + $xcut_pitch * ($columns - 1)],$upper_height)]
+      set upper_width [expr $cut_width + $xcut_pitch * ($columns - 1) + 2 * $min_upper_enclosure]
+    }
+  } 
+  if {[get_routing_direction [dict get $via_info lower layer]] == "ver"} {
+    if {[dict get $constraints stack_bottom] != [dict get $via_info lower layer]} {
+      # debug "Adjust width of [dict get $via_info lower layer]"
+      get_via_enclosure $via_info [expr min([expr $cut_width + $xcut_pitch * ($columns - 1)],$lower_height)] [expr min($upper_width,$upper_height)]
+      set lower_width [expr $cut_width + $xcut_pitch * ($columns - 1) + 2 * $min_lower_enclosure]
+    }
+  }
+  # debug "cols $columns W: lower $lower_width upper $upper_width"
+  set lower_width [get_adjusted_dX [dict get $via_info lower layer] $lower_width]
+  set upper_width [get_adjusted_dX [dict get $via_info upper layer] $upper_width]
+  # debug "cols $columns W: lower $lower_width upper $upper_width"
+
+  return $columns
+}
+
+proc determine_num_via_rows {via_info constraints} {
+  variable cut_height
+  variable ycut_pitch
+  variable ycut_spacing
+  variable upper_height
+  variable lower_height
+  variable lower_width
+  variable upper_width
+  variable lower_dir
+  variable upper_dir
+  variable min_lower_enclosure
+  variable max_lower_enclosure
+  variable min_upper_enclosure
+  variable max_upper_enclosure
+  variable def_units
+
+  # What are the maximum number of rows that we can fit in this space?
+  set i 1
+  if {$lower_dir == "hor"} {
+    set via_height_lower [expr $cut_height + $ycut_pitch * ($i - 1) + 2 * $min_lower_enclosure]
+    set via_height_upper [expr $cut_height + $ycut_pitch * ($i - 1) + 2 * $max_upper_enclosure]
+  } else {
+    set via_height_lower [expr $cut_height + $ycut_pitch * ($i - 1) + 2 * $max_lower_enclosure]
+    set via_height_upper [expr $cut_height + $ycut_pitch * ($i - 1) + 2 * $min_upper_enclosure]
+  }
+  if {[dict exists $constraints cut_pitch]} {set ycut_pitch [expr round([dict get $constraints cut_pitch] * $def_units)]}
+  while {$via_height_lower < $lower_height && $via_height_upper < $upper_height} {
+    incr i
+    if {$lower_dir == "hor"} {
+      set via_height_lower [expr $cut_height + $ycut_pitch * ($i - 1) + 2 * $min_lower_enclosure]
+      set via_height_upper [expr $cut_height + $ycut_pitch * ($i - 1) + 2 * $max_upper_enclosure]
+    } else {
+      set via_height_lower [expr $cut_height + $ycut_pitch * ($i - 1) + 2 * $max_lower_enclosure]
+      set via_height_upper [expr $cut_height + $ycut_pitch * ($i - 1) + 2 * $min_upper_enclosure]
+    }
+  }
+  set ycut_spacing [expr $ycut_pitch - $cut_height]
+  set rows [expr max(1,$i - 1)]
+  # debug "$rows H: $via_height_lower >= $lower_height && $via_height_upper >= $upper_height"
+  if {[dict exists $constraints max_rows]} {
+    if {$rows > [dict get $constraints max_rows]} {
+      set rows [dict get $constraints max_rows]
+
+      set lower_concave_enclosure [get_concave_spacing_value [dict get $via_info cut layer] [dict get $via_info lower layer]]
+      # debug "$lower_concave_enclosure $max_lower_enclosure"
+      if {$lower_concave_enclosure > $max_lower_enclosure} {
+        set max_lower_enclosure $lower_concave_enclosure
+      }
+      set upper_concave_enclosure [get_concave_spacing_value [dict get $via_info cut layer] [dict get $via_info upper layer]]
+      # debug "$upper_concave_enclosure $max_upper_enclosure"
+      if {$upper_concave_enclosure > $max_upper_enclosure} {
+        set max_upper_enclosure $upper_concave_enclosure
+      }
+
+    }
+  }
+  if {[get_routing_direction [dict get $via_info lower layer]] == "hor"} {
+    # debug "[dict get $constraints stack_bottom] != [dict get $via_info lower layer]"
+    if {[dict get $constraints stack_bottom] != [dict get $via_info lower layer]} {
+      # debug "Adjust height of [dict get $via_info lower layer]"
+      get_via_enclosure $via_info [expr min($lower_width,[expr $cut_height + $ycut_pitch * ($rows - 1)])] [expr min($upper_width,$upper_height)]
+      set lower_height [expr $cut_height + $ycut_pitch * ($rows - 1) + 2 * $min_lower_enclosure]
+      # debug "modify lower_height to $lower_height ($cut_height + $ycut_pitch * ($rows - 1) + 2 * $min_lower_enclosure"
+    }
+  } 
+  if {[get_routing_direction [dict get $via_info upper layer]] == "hor"} {
+    # debug "[dict get $constraints stack_top] != [dict get $via_info upper layer]"
+    if {[dict get $constraints stack_top] != [dict get $via_info upper layer]} {
+      # debug "Adjust height of [dict get $via_info upper layer]"
+      get_via_enclosure $via_info [expr min($lower_width,$lower_height)] [expr min($upper_width,[expr $cut_height + $ycut_pitch * ($rows - 1)])]
+      set upper_height [expr $cut_height + $ycut_pitch * ($rows - 1) + 2 * $min_upper_enclosure]
+      # debug "modify upper_height to $upper_height ($cut_height + $ycut_pitch * ($rows - 1) + 2 * $min_upper_enclosure"
+    }
+  }
+  # debug "$rows H: lower $lower_height upper $upper_height"
+  set lower_height [get_adjusted_dY [dict get $via_info lower layer] $lower_height]
+  set upper_height [get_adjusted_dY [dict get $via_info upper layer] $upper_height]
+  # debug "$rows H: lower $lower_height upper $upper_height"
+
+  return $rows
+}
+
+proc init_via_width_height {via_info lower_layer width height constraints} {
+  variable def_units
+  variable upper_width
+  variable lower_width
+  variable upper_height
+  variable lower_height
+  variable lower_dir
+  variable upper_dir
+  variable min_lower_enclosure
+  variable max_lower_enclosure
+  variable min_upper_enclosure
+  variable max_upper_enclosure
+  variable cut_width
+  variable cut_height
+  variable xcut_pitch
+  variable ycut_pitch
+  variable xcut_spacing
+  variable ycut_spacing
+
+  set upper_layer [dict get $via_info upper layer]
+
+  set xcut_pitch [lindex [dict get $via_info cut spacing] 0]
+  set ycut_pitch [lindex [dict get $via_info cut spacing] 0]
+
+  set cut_width   [lindex [dict get $via_info cut size] 0]
+  set cut_height  [lindex [dict get $via_info cut size] 1]
+
+  if {[dict exists $constraints split_cuts $lower_layer]} {
+    if {[get_dir $lower_layer] == "hor"} {
+      set ycut_pitch [expr round([dict get $constraints split_cuts $lower_layer] * $def_units)]
+    } else {
+      set xcut_pitch [expr round([dict get $constraints split_cuts $lower_layer] * $def_units)]
+    }
+  }
+
+  if {[dict exists $constraints split_cuts $upper_layer]} {
+    if {[get_dir $upper_layer] == "hor"} {
+      set ycut_pitch [expr round([dict get $constraints split_cuts $upper_layer] * $def_units)]
+    } else {
+      set xcut_pitch [expr round([dict get $constraints split_cuts $upper_layer] * $def_units)]
+    }
+  }
+
+  if {[dict exists $constraints width $lower_layer]} {
+    if {[get_dir $lower_layer] == "hor"} {
+      set lower_height [expr round([dict get $constraints width $lower_layer] * $def_units)]
+      set lower_width  [get_adjusted_dX $lower_layer $width]
+    } else {
+      set lower_width [expr round([dict get $constraints width $lower_layer] * $def_units)]
+      set lower_height [get_adjusted_dY $lower_layer $height]
+    }
+  } else {
+    # Adjust the width and height values to the next largest allowed value if necessary
+    set lower_width  [get_adjusted_dX $lower_layer $width]
+    set lower_height [get_adjusted_dY $lower_layer $height]
+  }
+  if {[dict exists $constraints width $upper_layer]} {
+    if {[get_dir $upper_layer] == "hor"} {
+      set upper_height [expr round([dict get $constraints width $upper_layer] * $def_units)]
+      set upper_width  [get_adjusted_dX $upper_layer $width]
+    } else {
+      set upper_width [expr round([dict get $constraints width $upper_layer] * $def_units)]
+      set upper_height [get_adjusted_dY $upper_layer $height]
+    }
+  } else {
+    set upper_width  [get_adjusted_dX $upper_layer $width]
+    set upper_height [get_adjusted_dY $upper_layer $height]
+  }
+  # debug "lower (width $lower_width height $lower_height) upper (width $upper_width height $upper_height)"
+  # debug "min - \[expr min($lower_width,$lower_height,$upper_width,$upper_height)\]"
+}
+
+proc get_enclosure_by_direction {layer xenc yenc max_enclosure min_enclosure} {
+  set info {}
+  if {$xenc > $max_enclosure && $yenc > $min_enclosure || $xenc > $min_enclosure && $yenc > $max_enclosure} {
+    # If the current enclosure values meet the min/max enclosure requirements either way round, then keep
+    # the current enclsoure settings
+    dict set info xEnclosure $xenc
+    dict set info yEnclosure $yenc
+  } else {
+    # Enforce min/max enclosure rule, with max_enclosure along the preferred direction of the layer.
+    if {[get_dir $layer] == "hor"} {
+      dict set info xEnclosure [expr max($xenc,$max_enclosure)]
+      dict set info yEnclosure [expr max($yenc,$min_enclosure)]
+    } else {
+      dict set info xEnclosure [expr max($xenc,$min_enclosure)]
+      dict set info yEnclosure [expr max($yenc,$max_enclosure)]
+    }
+  }
+
+  return $info
+}
+
+proc via_generate_rule {viarule_name via_info rule_name rows columns constraints} {
+  variable xcut_pitch
+  variable ycut_pitch
+  variable xcut_spacing
+  variable ycut_spacing
+  variable cut_height
+  variable cut_width
+  variable upper_width
+  variable lower_width
+  variable upper_height
+  variable lower_height
+  variable lower_dir
+  variable min_lower_enclosure
+  variable max_lower_enclosure
+  variable min_upper_enclosure
+  variable max_upper_enclosure
+
+  set lower_enc_width  [expr round(($lower_width  - ($cut_width   + $xcut_pitch * ($columns - 1))) / 2)]
+  set lower_enc_height [expr round(($lower_height - ($cut_height  + $ycut_pitch * ($rows    - 1))) / 2)]
+  set upper_enc_width  [expr round(($upper_width  - ($cut_width   + $xcut_pitch * ($columns - 1))) / 2)]
+  set upper_enc_height [expr round(($upper_height - ($cut_height  + $ycut_pitch * ($rows    - 1))) / 2)]
+
+  set lower [get_enclosure_by_direction [dict get $via_info lower layer] $lower_enc_width $lower_enc_height $max_lower_enclosure $min_lower_enclosure]
+  set upper [get_enclosure_by_direction [dict get $via_info upper layer] $upper_enc_width $upper_enc_height $max_upper_enclosure $min_upper_enclosure]
+  # debug "rule $rule_name"
+  # debug "lower: width $lower_width height $lower_height"
+  # debug "lower: enc_width $lower_enc_width enc_height $lower_enc_height enclosure_rule $max_lower_enclosure $min_lower_enclosure"
+  # debug "lower: enclosure [dict get $lower xEnclosure] [dict get $lower yEnclosure]"
+  # debug "upper: enc_width $upper_enc_width enc_height $upper_enc_height enclosure_rule $max_upper_enclosure $min_upper_enclosure"
+  # debug "upper: enclosure [dict get $upper xEnclosure] [dict get $upper yEnclosure]"
+
+  return [list [list \
+    name $rule_name \
+    rule $viarule_name \
+    cutsize [dict get $via_info cut size] \
+    layers [list [dict get $via_info lower layer] [dict get $via_info cut layer] [dict get $via_info upper layer]] \
+    cutspacing [list $xcut_spacing $ycut_spacing] \
+    rowcol [list $rows $columns] \
+    lower_rect [list [expr -1 * $lower_width / 2] [expr -1 * $lower_height / 2] [expr $lower_width / 2] [expr $lower_height / 2]] \
+    upper_rect [list [expr -1 * $upper_width / 2] [expr -1 * $upper_height / 2] [expr $upper_width / 2] [expr $upper_height / 2]] \
+    enclosure [list \
+      [dict get $lower xEnclosure] \
+      [dict get $lower yEnclosure] \
+      [dict get $upper xEnclosure] \
+      [dict get $upper yEnclosure] \
+    ] \
+    origin_x 0 origin_y 0
+  ]]
+}
+
+proc via_generate_array_rule {viarule_name via_info rule_name rows columns} {
+  variable xcut_pitch
+  variable ycut_pitch
+  variable xcut_spacing
+  variable ycut_spacing
+  variable cut_height
+  variable cut_width
+  variable upper_width
+  variable lower_width
+  variable upper_height
+  variable lower_height
+  variable min_lower_enclosure
+  variable max_lower_enclosure
+  variable min_upper_enclosure
+  variable max_upper_enclosure
+
+  # We need array vias -
+  # if the min(rows,columns) > ARRAYCUTS
+  #   determine which direction gives best number of CUTs wide using min(ARRAYCUTS)
+  #   After adding ARRAYs, is there space for more vias
+  #   Add vias to the rule with appropriate origin setting
+  # else
+  #   add a single via with min(rows,columns) cuts - hor/ver as required
+
+
+  set spacing_rule [get_arrayspacing_rule [dict get $via_info cut layer]]
+  set array_size [expr min($rows, $columns)]
+
+  set lower_enc_width  [expr round(($lower_width  - ($cut_width   + $xcut_pitch * ($columns - 1))) / 2)]
+  set lower_enc_height [expr round(($lower_height - ($cut_height  + $ycut_pitch * ($rows    - 1))) / 2)]
+  set upper_enc_width  [expr round(($upper_width  - ($cut_width   + $xcut_pitch * ($columns - 1))) / 2)]
+  set upper_enc_height [expr round(($upper_height - ($cut_height  + $ycut_pitch * ($rows    - 1))) / 2)]
+
+  if {$array_size > [lindex [dict keys [dict get $spacing_rule arraycuts]] end]} {
+    # debug "Multi-viaArrayspacing rule"
+    set use_array_size [lindex [dict keys [dict get $spacing_rule arraycuts]] 0]
+    foreach other_array_size [lrange [dict keys [dict get $spacing_rule arraycuts]] 1 end] {
+      if {$array_size % $use_array_size > $array_size % $other_array_size} {
+        set use_array_size $other_array_size
+      }
+    }
+    set num_arrays [expr $array_size / $use_array_size]
+    set array_spacing [expr max($xcut_spacing,$ycut_spacing,[dict get $spacing_rule arraycuts $use_array_size spacing])]
+
+    set rule [list \
+      rule $viarule_name \
+      cutsize [dict get $via_info cut size] \
+      layers [list [dict get $via_info lower layer] [dict get $via_info cut layer] [dict get $via_info upper layer]] \
+      cutspacing [list $xcut_spacing $ycut_spacing] \
+      lower_rect [list [expr -1 * $lower_width / 2] [expr -1 * $lower_height / 2] [expr $lower_width / 2] [expr $lower_height / 2]] \
+      upper_rect [list [expr -1 * $upper_width / 2] [expr -1 * $upper_height / 2] [expr $upper_width / 2] [expr $upper_height / 2]] \
+      origin_x 0 \
+      origin_y 0 \
+    ]
+    # debug "$rule"
+    set rule_list {}
+    if {$array_size == $rows} {
+      # Split into num_arrays rows of arrays
+      set array_min_size [expr [lindex [dict get $via_info cut size] 0] * $use_array_size + [dict get $spacing_rule cutspacing] * ($use_array_size - 1)]
+      set total_array_size [expr $array_min_size * $num_arrays + $array_spacing * ($num_arrays - 1)]
+      # debug "Split into $num_arrays rows of arrays"
+
+      set lower_enc_height [expr round(($lower_height - ($cut_height  + $ycut_pitch * ($use_array_size - 1))) / 2)]
+      set upper_enc_height [expr round(($upper_height - ($cut_height  + $ycut_pitch * ($use_array_size - 1))) / 2)]
+
+      set lower_enc [get_enclosure_by_direction [dict get $via_info lower layer] $lower_enc_width $lower_enc_height $max_lower_enclosure $min_lower_enclosure]
+      set upper_enc [get_enclosure_by_direction [dict get $via_info upper layer] $upper_enc_width $upper_enc_height $max_upper_enclosure $min_upper_enclosure]
+
+      dict set rule rowcol [list $use_array_size $columns]
+      dict set rule name "[dict get $via_info cut layer]_ARRAY_${use_array_size}X${columns}"
+      dict set rule enclosure [list \
+        [dict get $lower_enc xEnclosure] \
+        [dict get $lower_enc yEnclosure] \
+        [dict get $upper_enc xEnclosure] \
+        [dict get $upper_enc yEnclosure] \
+      ]
+
+      set y [expr $array_min_size / 2 - $total_array_size / 2]
+      for {set i 0} {$i < $num_arrays} {incr i} {
+        dict set rule origin_y $y
+        lappend rule_list $rule
+        set y [expr $y + $array_spacing + $array_min_size]
+      }
+    } else {
+      # Split into num_arrays columns of arrays
+      set array_min_size [expr [lindex [dict get $via_info cut size] 1] * $use_array_size + [dict get $spacing_rule cutspacing] * ($use_array_size - 1)]
+      set total_array_size [expr $array_min_size * $num_arrays + $array_spacing * ($num_arrays - 1)]
+      # debug "Split into $num_arrays columns of arrays"
+
+      set lower_enc_width  [expr round(($lower_width  - ($cut_width   + $xcut_pitch * ($use_array_size - 1))) / 2)]
+      set upper_enc_width  [expr round(($upper_width  - ($cut_width   + $xcut_pitch * ($use_array_size - 1))) / 2)]
+
+      set lower_enc [get_enclosure_by_direction [dict get $via_info lower layer] $lower_enc_width $lower_enc_height $max_lower_enclosure $min_lower_enclosure]
+      set upper_enc [get_enclosure_by_direction [dict get $via_info upper layer] $upper_enc_width $upper_enc_height $max_upper_enclosure $min_upper_enclosure]
+
+      dict set rule rowcol [list $rows $use_array_size]
+      dict set rule name "[dict get $via_info cut layer]_ARRAY_${rows}X${use_array_size}"
+      dict set rule enclosure [list \
+        [dict get $lower_enc xEnclosure] \
+        [dict get $lower_enc yEnclosure] \
+        [dict get $upper_enc xEnclosure] \
+        [dict get $upper_enc yEnclosure] \
+      ]
+
+      set x [expr $array_min_size / 2 - $total_array_size / 2]
+      for {set i 0} {$i < $num_arrays} {incr i} {
+        dict set rule origin_x $x
+        lappend rule_list $rule
+        set x [expr $x + $array_spacing + $array_min_size]
+      }
+    }
+  } else {
+    # debug "Arrayspacing rule"
+    set lower_enc [get_enclosure_by_direction [dict get $via_info lower layer] $lower_enc_width $lower_enc_height $max_lower_enclosure $min_lower_enclosure]
+    set upper_enc [get_enclosure_by_direction [dict get $via_info upper layer] $upper_enc_width $upper_enc_height $max_upper_enclosure $min_upper_enclosure]
+
+    set rule [list \
+      name $rule_name \
+      rule $viarule_name \
+      cutsize [dict get $via_info cut size] \
+      layers [list [dict get $via_info lower layer] [dict get $via_info cut layer] [dict get $via_info upper layer]] \
+      cutspacing [list $xcut_spacing $ycut_spacing] \
+      rowcol [list $rows $columns] \
+      enclosure [list \
+        [dict get $lower_enc xEnclosure] \
+        [dict get $lower_enc yEnclosure] \
+        [dict get $upper_enc xEnclosure] \
+        [dict get $upper_enc yEnclosure] \
+      ] \
+      origin_x 0 \
+      origin_y 0 \
+    ]
+    set rule_list [list $rule]
+  }
+
+  return $rule_list
+}
+
+proc via_split_cuts_rule {rule_name via_info rows columns constraints} {
+  variable tech
+  variable def_units
+  variable min_lower_enclosure
+  variable max_lower_enclosure
+  variable min_upper_enclosure
+  variable max_upper_enclosure
+  variable cut_width
+  variable cut_height
+  variable xcut_pitch
+  variable ycut_pitch
+  variable xcut_spacing
+  variable ycut_spacing
+
+  set lower_rects {}
+  set cut_rects   {}
+  set upper_rects {}
+
+  set lower [dict get $via_info lower layer]
+  set upper [dict get $via_info upper layer]
+  # debug $via_info
+  # debug "lower $lower upper $upper"
+
+  set rule {}
+  set rule [list \
+    rule $rule_name \
+    cutsize [dict get $via_info cut size] \
+    layers [list $lower [dict get $via_info cut layer] $upper] \
+    cutspacing [list $xcut_spacing $ycut_spacing] \
+    rowcol [list 1 1] \
+  ]
+
+  # Enclosure was calculated from full width of intersection - need to recalculate for min cut size.
+  get_via_enclosure $via_info 0 0
+
+  # Area is stored in real units, adjust to def_units
+  set lower_area [expr round([[find_layer $lower] getArea] * $def_units * $def_units)]
+  set upper_area [expr round([[find_layer $upper] getArea] * $def_units * $def_units)]
+
+  if {[get_dir $lower] == "hor"} {
+    set lower_height [expr $cut_height + $min_lower_enclosure]
+    set lower_width  [expr $cut_width  + $max_lower_enclosure]
+    set upper_height [expr $cut_height + $max_upper_enclosure]
+    set upper_width  [expr $cut_width  + $min_upper_enclosure]
+
+    if {[dict exists $constraints split_cuts $lower]} {
+      set lower_width  [expr $lower_area / $lower_height]
+      if {$lower_width % 2 == 1} {incr lower_width}
+      set max_lower_enclosure [expr max(($lower_width - $cut_width) / 2, $max_lower_enclosure)]
+    }
+
+    if {[dict exists $constraints split_cuts $upper]} {
+      set upper_height [expr $upper_area / $upper_width]
+      if {$upper_height % 2 == 1} {incr upper_height}
+      set max_upper_enclosure [expr max(($upper_height - $cut_height) / 2, $max_upper_enclosure)]
+    }
+
+    set width [expr $max_lower_enclosure * 2 + $cut_width]
+    set height [expr $max_upper_enclosure * 2 + $cut_width]
+
+    dict set rule name [get_viarule_name $lower $width $height]
+    dict set rule enclosure [list $max_lower_enclosure $min_lower_enclosure $min_upper_enclosure $max_upper_enclosure]
+  } else {
+    set lower_height [expr $cut_height + $max_lower_enclosure]
+    set lower_width  [expr $cut_width  + $min_lower_enclosure]
+    set upper_height [expr $cut_height + $min_upper_enclosure]
+    set upper_width  [expr $cut_width  + $max_upper_enclosure]
+
+    if {[dict exists $constraints split_cuts $lower]} {
+      set lower_width  [expr $cut_width + $min_lower_enclosure]
+      set lower_height [expr $cut_width + $max_lower_enclosure]
+      set min_lower_length [expr $lower_area / $lower_width]
+      if {$min_lower_length % 2 == 1} {incr min_lower_length}
+      set max_lower_enclosure [expr max(($min_lower_length - $cut_width) / 2, $max_lower_enclosure)]
+    }
+
+    if {[dict exists $constraints split_cuts $upper]} {
+      set upper_width  [expr $cut_height + $max_upper_enclosure]
+      set upper_height [expr $cut_height + $min_upper_enclosure]
+      set min_upper_length [expr $upper_area / $upper_height]
+      if {$min_upper_length % 2 == 1} {incr min_upper_length}
+      set max_upper_enclosure [expr max(($min_upper_length - $cut_height) / 2, $max_upper_enclosure)]
+    }
+
+    set width [expr $max_upper_enclosure * 2 + $cut_width]
+    set height [expr $max_lower_enclosure * 2 + $cut_width]
+
+    dict set rule name [get_viarule_name $lower $width $height]
+    dict set rule enclosure [list $min_lower_enclosure $max_lower_enclosure $max_upper_enclosure $min_upper_enclosure]
+  }
+  dict set rule lower_rect [list [expr -1 * $lower_width / 2] [expr -1 * $lower_height / 2] [expr $lower_width / 2] [expr $lower_height / 2]]
+  dict set rule upper_rect [list [expr -1 * $upper_width / 2] [expr -1 * $upper_height / 2] [expr $upper_width / 2] [expr $upper_height / 2]]
+  # debug "min_lower_enclosure $min_lower_enclosure"
+  # debug "lower $lower upper $upper enclosure [dict get $rule enclosure]"
+
+  for {set i 0} {$i < $rows} {incr i} {
+    for {set j 0} {$j < $columns} {incr j} {
+      set centre_x [expr round(($j - (($columns - 1) / 2.0)) * $xcut_pitch)]
+      set centre_y [expr round(($i - (($rows - 1)    / 2.0)) * $ycut_pitch)]
+
+      dict set rule origin_x $centre_x
+      dict set rule origin_y $centre_y
+      lappend rule_list $rule
+    }
+  }
+  # debug "split into [llength $rule_list] vias"
+  return $rule_list
+}
+
+# viarule structure:
+# {
+#    name <via_name>
+#    rule <via_rule_name>
+#    cutsize {<cut_size>}
+#    layers {<lower> <cut> <upper>}
+#    cutspacing {<x_spacing> <y_spacing>}
+#    rowcol {<rows> <columns>}
+#    origin_x <x_location>
+#    origin_y <y_location>
+#    enclosure {<x_lower_enclosure> <y_lower_enclosure> <x_upper_enclosure> <y_upper_enclosure>}
+#    lower_rect {<llx> <lly> <urx> <ury>}
+#  }
+
+# Given the via rule expressed in via_info, what is the via with the largest cut area that we can make
+# Try using a via generate rule
+proc get_via_option {viarule_name via_info lower width height constraints} {
+  variable upper_width
+  variable lower_width
+  variable upper_height
+  variable lower_height
+  variable lower_dir
+  variable upper_dir
+  variable min_lower_enclosure
+  variable max_lower_enclosure
+  variable min_upper_enclosure
+  variable max_upper_enclosure
+  variable default_cutclass
+  variable grid_data
+  variable via_location
+  variable def_units
+
+  set upper [dict get $via_info upper layer]
+
+  # debug "{$lower $width $height}"
+
+  set lower_dir [get_dir $lower]
+  set upper_dir [get_dir $upper]
+
+  init_via_width_height $via_info $lower $width $height $constraints
+  # debug "lower: $lower, width: $width, height: $height, lower_width: $lower_width, lower_height: $lower_height"
+  get_via_enclosure $via_info [expr min($lower_width,$lower_height)] [expr min($upper_width,$upper_height)]
+
+  # debug "split cuts? [dict exists $constraints split_cuts]"
+  # debug "lower $lower upper $upper"
+  # debug [dict get $via_info cut layer]
+
+  # Determines the maximum number of rows and columns that can fit into this width/height
+  set columns [determine_num_via_columns $via_info $constraints]
+  set rows    [determine_num_via_rows    $via_info $constraints]
+
+  # debug "columns: $columns, rows: $rows"	  
+  # debug "lower_width $lower_width lower_height: $lower_height, min_lower_enclosure $min_lower_enclosure"
+  # debug "upper_width $upper_width upper_height: $upper_height, min_upper_enclosure $min_upper_enclosure"
+
+  if {[dict exists $constraints split_cuts] && ([lsearch -exact [dict get $constraints split_cuts] $lower] > -1 || [lsearch -exact [dict get $constraints split_cuts] $upper] > -1)} {
+    # debug "via_split_cuts_rule"
+    set rules [via_split_cuts_rule $viarule_name $via_info $rows $columns $constraints]
+  } elseif {[use_arrayspacing [dict get $via_info cut layer] $rows $columns]} {
+    # debug "via_generate_array_rule"
+    set rules [via_generate_array_rule $viarule_name $via_info [get_viarule_name $lower $width $height] $rows $columns]
+  } else {
+    # debug "via_generate_rule"
+    set rules [via_generate_rule $viarule_name $via_info [get_viarule_name $lower $width $height] $rows $columns $constraints]
+  }
+
+  # Check minimum_cuts
+  set checked_rules {}
+  foreach via_rule $rules {
+    # debug "$via_rule"
+    set num_cuts [expr [lindex [dict get $via_rule rowcol] 0] * [lindex [dict get $via_rule rowcol] 1]]
+    if {[dict exists $default_cutclass [lindex [dict get $via_rule layers] 1]]} {
+      set cut_class [dict get $default_cutclass [lindex [dict get $via_rule layers] 1]]
+    } else {
+      set cut_class "NONE"
+    }
+    set lower_layer [lindex [dict get $via_rule layers] 0]
+    if {[dict exists $constraints stack_bottom]} {
+      if {[dict exists $grid_data straps $lower_layer] || [dict exists $grid_data rails $lower_layer]} {
+        set lower_width [get_grid_wire_width $lower_layer]
+      } else {
+        set lower_rect [dict get $via_rule lower_rect]
+        set lower_width [expr min(([lindex $lower_rect 2] - [lindex $lower_rect 0]), ([lindex $lower_rect 3] - [lindex $lower_rect 1]))]
+      }
+    } else {
+      set lower_rect [dict get $via_rule lower_rect]
+      set lower_width [expr min(([lindex $lower_rect 2] - [lindex $lower_rect 0]), ([lindex $lower_rect 3] - [lindex $lower_rect 1]))]
+    }
+    set min_cut_rule [get_minimumcuts $lower_layer $lower_width fromabove $cut_class]
+    if {$num_cuts < $min_cut_rule} {
+      utl::warn "PDN" 38 "Illegal via: number of cuts ($num_cuts), does not meet minimum cut rule ($min_cut_rule) for $lower_layer to $cut_class with width [expr 1.0 * $lower_width / $def_units]."
+      dict set via_rule illegal 1
+    } else {
+      # debug "Legal number of cuts ($num_cuts) meets minimum cut rule ($min_cut_rule) for $lower_layer, $lower_width, $cut_class"
+    }
+
+    set upper_layer [lindex [dict get $via_rule layers] 2]
+    if {[dict exists $constraints stack_top]} {
+      if {[dict exists $grid_data straps $upper_layer width] || [dict exists $grid_data rails $upper_layer width]} {
+        set upper_width [get_grid_wire_width $upper_layer]
+      } else {
+        set upper_rect [dict get $via_rule upper_rect]
+        set upper_width [expr min(([lindex $upper_rect 2] - [lindex $upper_rect 0]), ([lindex $upper_rect 3] - [lindex $upper_rect 1]))]
+      }
+    } else {
+      set upper_rect [dict get $via_rule upper_rect]
+      set upper_width [expr min(([lindex $upper_rect 2] - [lindex $upper_rect 0]), ([lindex $upper_rect 3] - [lindex $upper_rect 1]))]
+    }
+    set min_cut_rule [get_minimumcuts $upper_layer $upper_width frombelow $cut_class]
+
+    if {$num_cuts < $min_cut_rule} {
+      utl::warn "PDN" 39 "Illegal via: number of cuts ($num_cuts), does not meet minimum cut rule ($min_cut_rule) for $upper_layer to $cut_class with width [expr 1.0 * $upper_width / $def_units]."
+      dict set via_rule illegal 1
+    } else {
+      # debug "Legal number of cuts ($num_cuts) meets minimum cut rule ($min_cut_rule) for $upper_layer, $upper_width $cut_class"
+    }
+    if {[dict exists $via_rule illegal]} {
+      utl::warn "PDN" 36 "Attempt to add illegal via at : ([expr 1.0 * [lindex $via_location 0] / $def_units] [expr 1.0 * [lindex $via_location 1] / $def_units]), via will not be added."
+    }
+    lappend checked_rules $via_rule
+  }
+
+  return $checked_rules
+}
+
+proc get_viarule_name {lower width height} {
+  set rules [select_via_info $lower]
+  if {[llength $rules] > 0} {
+    set first_key [lindex [dict keys $rules] 0]
+    #if {![dict exists $rules $first_key cut layer]} {
+    #  debug "$lower $width $height"
+    #  debug "$rules"
+    #  debug "$first_key"
+    #}
+    set cut_layer [dict get $rules $first_key cut layer]
+  } else {
+    set cut_layer $lower
+  }
+
+  return ${cut_layer}_${width}x${height}
+}
+
+proc get_cut_area {rule} {
+  set area 0
+  foreach via $rule {
+    set area [expr [lindex [dict get $via rowcol] 0] * [lindex [dict get $via rowcol] 0] * [lindex [dict get $via cutsize] 0] * [lindex [dict get $via cutsize] 1]]
+  }
+  return $area
+}
+
+proc select_rule {rule1 rule2} {
+  if {[get_cut_area $rule2] > [get_cut_area $rule1]} {
+    return $rule2
+  }
+  return $rule1
+}
+
+proc connection_specifies_fixed_via {constraints lower} {
+  if {[dict exists $constraints use_fixed_via]} {
+    return [dict exists $constraints use_fixed_via $lower]
+  }
+  return 0
+}
+
+proc get_via {lower width height constraints} {
+  # First cur will assume that all crossing points (x y) are on grid for both lower and upper layers
+  # TODO: Refine the algorithm to cope with offgrid intersection points
+  variable physical_viarules
+
+  set rule_name [get_viarule_name $lower $width $height]
+
+  if {![dict exists $physical_viarules $rule_name]} {
+    set selected_rule {}
+    # debug "$constraints"
+    if {[connection_specifies_fixed_via $constraints $lower]} {
+      # debug "Using fixed_via for $rule_name"
+      set via_name [dict get $constraints use_fixed_via $lower]
+      dict set physical_viarules $rule_name [list [list name $via_name fixed $via_name origin_x 0 origin_y 0 layers [list $lower "cut" "upper"]]]
+    } else {
+      dict for {name rule} [select_via_info $lower] {
+        set result [get_via_option $name $rule $lower $width $height $constraints]
+        if {$selected_rule == {}} {
+          set selected_rule $result
+        } else {
+          # Choose the best between selected rule and current result, the winner becomes the new selected rule
+          set selected_rule [select_rule $selected_rule $result]
+        }
+      }
+      dict set physical_viarules $rule_name $selected_rule
+      # debug "Via [dict size $physical_viarules]: $rule_name"
+    }
+  }
+
+  return $rule_name
+}
+
+proc instantiate_via {physical_via_name x y constraints} {
+  variable physical_viarules
+  variable block
+  variable layers
+
+  set via_insts {}
+
+  foreach via [dict get $physical_viarules $physical_via_name] {
+    # debug "via x $x y $y $via"
+
+    # Dont instantiate illegal vias
+    if {[dict exists $via illegal]} {continue}
+
+    set x_location [expr $x + [dict get $via origin_x]]
+    set y_location [expr $y + [dict get $via origin_y]]
+
+    set lower_layer_name [lindex [dict get $via layers] 0]
+    set upper_layer_name [lindex [dict get $via layers] 2]
+
+    if {[dict exists $constraints ongrid]} {
+      if {[lsearch -exact [dict get $constraints ongrid] $lower_layer_name] > -1} {
+        if {[get_dir $lower_layer_name] == "hor"} {
+          set y_pitch [dict get $layers $lower_layer_name pitch]
+          set y_offset [dict get $layers $lower_layer_name offsetY]
+
+          set y_location [expr ($y - $y_offset + $y_pitch / 2) / $y_pitch * $y_pitch + $y_offset + [dict get $via origin_y]]
+        } else {
+          set x_pitch [dict get $layers $lower_layer_name pitch]
+          set x_offset [dict get $layers $lower_layer_name offsetX]
+
+          set x_location [expr ($x - $x_offset + $x_pitch / 2) / $x_pitch * $x_pitch + $x_offset + [dict get $via origin_x]]
+        }
+      }
+      if {[lsearch -exact [dict get $constraints ongrid] $upper_layer_name] > -1} {
+        if {[get_dir $lower_layer_name] == "hor"} {
+          set x_pitch [dict get $layers $upper_layer_name pitch]
+          set x_offset [dict get $layers $upper_layer_name offsetX]
+
+          set x_location [expr ($x - $x_offset + $x_pitch / 2) / $x_pitch * $x_pitch + $x_offset + [dict get $via origin_x]]
+        } else {
+          set y_pitch [dict get $layers $upper_layer_name pitch]
+          set y_offset [dict get $layers $upper_layer_name offsetY]
+
+          set y_location [expr ($y - $y_offset + $y_pitch / 2) / $y_pitch * $y_pitch + $y_offset + [dict get $via origin_y]]
+        }
+      }
+    }
+    # debug "x: $x -> $x_location"
+    # debug "y: $y -> $y_location"
+
+    dict set via x $x_location
+    dict set via y $y_location
+
+    lappend via_insts $via
+  }
+  return $via_insts
+}
+
+proc generate_vias {layer1 layer2 intersections connection} {
+  variable logical_viarules
+  variable metal_layers
+  variable via_location
+  variable tech
+
+  set constraints {}
+  if {[dict exists $connection constraints]} {
+    set constraints [dict get $connection constraints]
+  }
+  if {[dict exists $connection fixed_vias]} {
+    foreach via_name [dict get $connection fixed_vias] {
+      if {[set via [$tech findVia $via_name]] != "NULL"} {
+        set lower_layer_name [[$via getBottomLayer] getName]
+        dict set constraints use_fixed_via $lower_layer_name $via_name
+      } else {
+        utl::warn "PDN" 63 "Via $via_name specified in the grid specification does not exist in this technology."
+      }
+    }
+  }
+
+  # debug "    Constraints: $constraints"
+  set vias {}
+  set layer1_name $layer1
+  set layer2_name $layer2
+  regexp {(.*)_PIN_(hor|ver)} $layer1 - layer1_name layer1_direction
+
+  set i1 [lsearch -exact $metal_layers $layer1_name]
+  set i2 [lsearch -exact $metal_layers $layer2_name]
+  if {$i1 == -1} {utl::error "PDN" 22 "Cannot find lower metal layer $layer1."}
+  if {$i2 == -1} {utl::error "PDN" 23 "Cannot find upper metal layer $layer2."}
+
+  # For each layer between l1 and l2, add vias at the intersection
+  # debug "  # Intersections [llength $intersections]"
+  set count 0
+  foreach intersection $intersections {
+    if {![dict exists $logical_viarules [dict get $intersection rule]]} {
+      utl::error "PDN" 24 "Missing logical viarule [dict get $intersection rule].\nAvailable logical viarules [dict keys $logical_viarules]."
+    }
+    set logical_rule [dict get $logical_viarules [dict get $intersection rule]]
+
+    set x [dict get $intersection x]
+    set y [dict get $intersection y]
+    set width  [dict get $logical_rule width]
+    set height  [dict get $logical_rule height]
+    set via_location [list $x $y]
+
+    set connection_layers [lrange $metal_layers $i1 [expr $i2 - 1]]
+    # debug "  # Connection layers: [llength $connection_layers]"
+    # debug "  Connection layers: $connection_layers"
+    dict set constraints stack_top $layer2_name
+    dict set constraints stack_bottom $layer1_name
+    foreach lay $connection_layers {
+      set via_name [get_via $lay $width $height $constraints]
+      foreach via [instantiate_via $via_name $x $y $constraints] {
+        lappend vias $via
+      }
+    }
+
+    incr count
+    #if {$count % 1000 == 0} {
+    #  debug "  # $count / [llength $intersections]"
+    #}
+  }
+
+  return $vias
+}
+
+proc get_layers_from_to {from to} {
+  variable metal_layers
+
+  set layers {}
+  for {set i [lsearch -exact $metal_layers $from]} {$i <= [lsearch -exact $metal_layers $to]} {incr i} {
+    lappend layers [lindex $metal_layers $i]
+  }
+  return $layers
+}
+
+proc get_grid_channel_layers {} {
+  variable grid_data
+
+  set channel_layers {}
+  if {[dict exists $grid_data rails]} {
+    lappend channel_layers [lindex [dict keys [dict get $grid_data rails]] end]
+  }
+  foreach layer_name [dict keys [dict get $grid_data straps]] {
+    lappend channel_layers $layer_name
+  }
+
+  return $channel_layers
+}
+
+proc get_grid_channel_spacing {layer_name parallel_length} {
+  variable grid_data
+  variable def_units
+
+  if {[dict exists $grid_data straps $layer_name channel_spacing]} {
+    return [expr round([dict get $grid_data straps $layer_name channel_spacing] * $def_units)]
+  } elseif {[dict exists $grid_data straps $layer_name] && [dict exists $grid_data template names]} {
+    set template_name [lindex [dict get $grid_data template names] 0]
+    if {[dict exists $grid_data straps $layer_name $template_name channel_spacing]} {
+      return [expr round([dict get $grid_data straps $layer_name $template_name channel_spacing]]
+    }
+  } else {
+    set layer [[ord::get_db_tech] findLayer $layer_name]
+    if {$layer == "NULL"}  {
+      utl::error PDN 168 "Layer $layer_name does not exist"
+    }
+    set layer_width [get_grid_wire_width $layer_name]
+    if {[$layer hasTwoWidthsSpacingRules]} {
+      set num_widths [$layer getTwoWidthsSpacingTableNumWidths]
+      set current_width 0
+      set prl_rule -1
+      for {set rule 0} {$rule < $num_widths} {incr rule} {
+        set width [$layer getTwoWidthsSpacingTableWidth $rule]
+        if {$width == $current_width && $prl_rule != -1} {
+          continue
+        } else {
+          set current_width $width
+          if {[$layer getTwoWidthsSpacingTableHasPRL $rule] == 0} {
+            set non_prl_rule $rule
+            set prl_rule -1
+          } else {
+            if {$parallel_length > [$layer getTwoWidthsSpacingTablePRL $rule]} {
+              set prl_rule $rule
+            }
+          }
+        }
+        if {$layer_width < [$layer getTwoWidthsSpacingTableWidth $rule]} {
+          if {$prl_rule == 0} {
+            set use_rule $non_prl_rule
+          } else {
+            set use_rule $prl_rule
+          }
+          break
+        }
+      }
+
+      set spacing [$layer getTwoWidthsSpacingTableEntry $use_rule $use_rule]
+      # debug "Two widths spacing: layer: $layer_name, rule: $use_rule, spacing: $spacing"
+    } elseif {[$layer hasV55SpacingRules]} {
+      set layer_width [get_grid_wire_width $layer_name]
+      set spacing [$layer findV55Spacing $layer_width $parallel_length]
+    } else {
+      set spacing [$layer getSpacing]
+    }
+    # Can't store value, since it depends on channel height
+    return $spacing
+  }
+
+  utl::error "PDN" 52 "Unable to get channel_spacing setting for layer $layer_name."
+}
+
+proc get_grid_wire_width {layer_name} {
+  variable grid_data
+  variable default_grid_data
+  variable design_data
+
+  if {[info exists grid_data]} {
+    if {[dict exists $grid_data rails $layer_name width]} {
+      set width [dict get $grid_data rails $layer_name width]
+      return $width
+    } elseif {[dict exists $grid_data straps $layer_name width]} {
+      set width [dict get $grid_data straps $layer_name width]
+      return $width
+    } elseif {[dict exists $grid_data straps $layer_name] && [dict exists $grid_data template names]} {
+      set template_name [lindex [dict get $grid_data template names] 0]
+      set width [dict get $grid_data straps $layer_name $template_name width]
+      return $width
+    } elseif {[dict exists $grid_data core_ring $layer_name width]} {
+      set width [dict get $grid_data core_ring $layer_name width]
+      return $width
+    }
+  }
+
+  if {[info exists default_grid_data]} {
+    if {[dict exists $default_grid_data rails $layer_name width]} {
+      set width [dict get $default_grid_data rails $layer_name width]
+      return $width
+    } elseif {[dict exists $default_grid_data straps $layer_name width]} {
+      set width [dict get $default_grid_data straps $layer_name width]
+      return $width
+    } elseif {[dict exists $default_grid_data straps $layer_name] && [dict exists $default_grid_data template names]} {
+      set template_name [lindex [dict get $default_grid_data template names] 0]
+      set width [dict get $default_grid_data straps $layer_name $template_name width]
+      return $width
+    }
+  }
+  utl::error "PDN" 44 "No width information found for $layer_name."
+}
+
+proc get_grid_wire_pitch {layer_name} {
+  variable grid_data
+  variable default_grid_data
+  variable design_data
+
+  if {[dict exists $grid_data rails $layer_name pitch]} {
+    set pitch [dict get $grid_data rails $layer_name pitch]
+  } elseif {[dict exists $grid_data straps $layer_name pitch]} {
+    set pitch [dict get $grid_data straps $layer_name pitch]
+  } elseif {[dict exists $grid_data straps $layer_name] && [dict exists $grid_data template names]} {
+    set template_name [lindex [dict get $grid_data template names] 0]
+    set pitch [dict get $grid_data straps $layer_name $template_name pitch]
+  } elseif {[dict exists $default_grid_data straps $layer_name pitch]} {
+    set pitch [dict get $default_grid_data straps $layer_name pitch]
+  } elseif {[dict exists $default_grid_data straps $layer_name] && [dict exists $default_grid_data template names]} {
+    set template_name [lindex [dict get $default_grid_data template names] 0]
+    set pitch [dict get $default_grid_data straps $layer_name $template_name pitch]
+  } else {
+    utl::error "PDN" 45 "No pitch information found for $layer_name."
+  }
+
+  return $pitch
+}
+
+## Proc to generate via locations, both for a normal via and stacked via
+proc generate_via_stacks {l1 l2 tag connection} {
+  variable logical_viarules
+  variable stripe_locs
+  variable def_units
+  variable grid_data
+
+  set area [dict get $grid_data area]
+  # debug "From $l1 to $l2"
+
+  if {[dict exists $grid_data core_ring_area combined]} {
+    set grid_area [dict get $grid_data core_ring_area combined]
+    set factor [expr max([lindex $area 2] - [lindex $area 0], [lindex $area 3] - [lindex $area 1]) * 2]
+    set grid_area [odb::shrinkSet [odb::bloatSet $grid_area $factor] $factor]
+    # debug "Old area ($area)"
+    set bbox [lindex [odb::getRectangles $grid_area] 0]
+    set area [list {*}[$bbox ll] {*}[$bbox ur]]
+    # debug "Recalculated area to be ($area)"
+  }
+
+  #this variable contains locations of intersecting points of two orthogonal metal layers, between which via needs to be inserted
+  #for every intersection. Here l1 and l2 are layer names, and i1 and i2 and their indices, tag represents domain (power or ground)
+  set intersections ""
+  #check if layer pair is orthogonal, case 1
+  set layer1 $l1
+  regexp {(.*)_PIN_(hor|ver)} $l1 - layer1 direction
+
+  set layer2 $l2
+
+  set ignore_count 0
+  if {[array names stripe_locs "$l1,$tag"] == ""} {
+    utl::warn "PDN" 2 "No shapes on layer $l1 for $tag."
+    return {}
+  }
+  if {[array names stripe_locs "$l2,$tag"] == ""} {
+    utl::warn "PDN" 3 "No shapes on layer $l2 for $tag."
+    return {}
+  }
+  set intersection [odb::andSet [odb::andSet $stripe_locs($l1,$tag) $stripe_locs($l2,$tag)] [odb::newSetFromRect {*}$area]]
+
+  # debug "Detected [llength [::odb::getPolygons $intersection]] intersections of $l1 and $l2"
+
+  foreach shape [::odb::getPolygons $intersection] {
+    set points [::odb::getPoints $shape]
+    if {[llength $points] != 4} {
+        variable def_units
+        utl::warn "PDN" 4 "Unexpected number of points in connection shape ($l1,$l2 $tag [llength $points])."
+        set str "    "
+        foreach point $points {set str "$str ([expr 1.0 * [$point getX] / $def_units ] [expr 1.0 * [$point getY] / $def_units]) "}
+        utl::warn "PDN" 5 $str
+        continue
+    }
+    set xMin [expr min([[lindex $points 0] getX], [[lindex $points 1] getX], [[lindex $points 2] getX], [[lindex $points 3] getX])]
+    set xMax [expr max([[lindex $points 0] getX], [[lindex $points 1] getX], [[lindex $points 2] getX], [[lindex $points 3] getX])]
+    set yMin [expr min([[lindex $points 0] getY], [[lindex $points 1] getY], [[lindex $points 2] getY], [[lindex $points 3] getY])]
+    set yMax [expr max([[lindex $points 0] getY], [[lindex $points 1] getY], [[lindex $points 2] getY], [[lindex $points 3] getY])]
+
+    set width [expr $xMax - $xMin]
+    set height [expr $yMax - $yMin]
+
+    # Ensure that the intersections are not partial
+    if {![regexp {(.*)_PIN_(hor|ver)} $l1]} {
+      if {[get_dir $layer1] == "hor"} {
+        if {$height < [get_grid_wire_width $layer1]} {
+          # If the intersection doesnt cover the whole width of the bottom level wire, then ignore
+          utl::warn "PDN" 40 "No via added at ([expr 1.0 * $xMin / $def_units] [expr 1.0 * $yMin / $def_units] [expr 1.0 * $xMax / $def_units] [expr 1.0 * $yMax / $def_units]) because the full height of $layer1 ([expr 1.0 * [get_grid_wire_width $layer1] / $def_units]) is not covered by the overlap."
+          continue
+        }
+      } else {
+        if {$width < [get_grid_wire_width $layer1]} {
+          # If the intersection doesnt cover the whole width of the bottom level wire, then ignore
+          utl::warn "PDN" 41 "No via added at ([expr 1.0 * $xMin / $def_units] [expr 1.0 * $yMin / $def_units] [expr 1.0 * $xMax / $def_units] [expr 1.0 * $yMax / $def_units]) because the full width of $layer1 ([expr 1.0 * [get_grid_wire_width $layer1] / $def_units]) is not covered by the overlap."
+          continue
+        }
+      }
+    }
+    if {[get_dir $layer2] == "hor"} {
+      if {$height < [get_grid_wire_width $layer2]} {
+        # If the intersection doesnt cover the whole width of the top level wire, then ignore
+        utl::warn "PDN" 42 "No via added at ([expr 1.0 * $xMin / $def_units] [expr 1.0 * $yMin / $def_units] [expr 1.0 * $xMax / $def_units] [expr 1.0 * $yMax / $def_units]) because the full height of $layer2 ([expr 1.0 * [get_grid_wire_width $layer2] / $def_units]) is not covered by the overlap."
+        continue
+      }
+    } else {
+      if {$width < [get_grid_wire_width $layer2]} {
+        # If the intersection doesnt cover the whole width of the top level wire, then ignore
+        utl::warn "PDN" 43 "No via added at ([expr 1.0 * $xMin / $def_units] [expr 1.0 * $yMin / $def_units] [expr 1.0 * $xMax / $def_units] [expr 1.0 * $yMax / $def_units]) because the full width of $layer2 ([expr 1.0 * [get_grid_wire_width $layer2] / $def_units]) is not covered by the overlap."
+        continue
+      }
+    }
+
+    set rule_name ${l1}${layer2}_${width}x${height}
+    if {![dict exists $logical_viarules $rule_name]} {
+      dict set logical_viarules $rule_name [list lower $l1 upper $layer2 width $width height $height]
+    }
+    lappend intersections "rule $rule_name x [expr ($xMax + $xMin) / 2] y [expr ($yMax + $yMin) / 2]"
+  }
+
+  # debug "Added [llength $intersections] intersections"
+
+  return [generate_vias $l1 $l2 $intersections $connection]
+}
+
+proc add_stripe {layer type polygon_set} {
+  variable stripes
+  # debug "start"
+  lappend stripes($layer,$type) $polygon_set
+  # debug "end"
+}
+
+proc merge_stripes {} {
+  variable stripes
+  variable stripe_locs
+
+  foreach stripe_set [array names stripes] {
+    # debug "$stripe_set [llength $stripes($stripe_set)]"
+    if {[llength $stripes($stripe_set)] > 0} {
+      set merged_stripes [shapes_to_polygonSet $stripes($stripe_set)]
+      if {[array names stripe_locs $stripe_set] != ""} {
+        # debug "$stripe_locs($stripe_set)"
+        set stripe_locs($stripe_set) [odb::orSet $stripe_locs($stripe_set) $merged_stripes]
+      } else {
+        set stripe_locs($stripe_set) $merged_stripes
+      }
+    }
+    set stripes($stripe_set) {}
+  }
+}
+
+proc get_core_ring_vertical_layer_name {} {
+  variable grid_data
+
+  if {![dict exists $grid_data core_ring]} {
+    return ""
+  }
+
+  foreach layer_name [dict keys [dict get $grid_data core_ring]] {
+    if {[get_dir $layer_name] == "ver"} {
+      return $layer_name
+    }
+  }
+
+  return ""
+}
+
+proc is_extend_to_core_ring {layer_name} {
+  variable grid_data
+
+  if {![dict exists $grid_data rails $layer_name extend_to_core_ring]} {
+    return 0
+  }
+  if {![dict get $grid_data rails $layer_name extend_to_core_ring]} {
+    return 0
+  }
+  if {[get_core_ring_vertical_layer_name] == ""} {
+    return 0
+  }
+  return 1
+}
+
+# proc to generate follow pin layers or standard cell rails
+proc generate_lower_metal_followpin_rails {} {
+  variable block
+  variable grid_data
+  variable design_data
+
+  set stdcell_area [get_extent [get_stdcell_area]]
+  set stdcell_min_x [lindex $stdcell_area 0]
+  set stdcell_max_x [lindex $stdcell_area 2]
+
+  if {[set ring_vertical_layer [get_core_ring_vertical_layer_name]] != ""} {
+    # debug "Ring vertical layer: $ring_vertical_layer"
+    # debug "Grid_data: $grid_data"
+    if {[dict exists $grid_data core_ring $ring_vertical_layer pad_offset]} {
+      set pad_area [find_pad_offset_area]
+      set offset [expr [dict get $grid_data core_ring $ring_vertical_layer pad_offset]]
+      set ring_adjustment [expr $stdcell_min_x - ([lindex $pad_area 0] + $offset)]
+    }
+    if {[dict exists $grid_data core_ring $ring_vertical_layer core_offset]} {
+      set ring_adjustment [expr \
+        [dict get $grid_data core_ring $ring_vertical_layer core_offset] + \
+        [dict get $grid_data core_ring $ring_vertical_layer spacing] + \
+        3 * [dict get $grid_data core_ring $ring_vertical_layer width] / 2 \
+      ]
+    }
+  }
+
+  foreach row [$block getRows] {
+    set orient [$row getOrient]
+    set box [$row getBBox]
+    switch -exact $orient {
+      R0 {
+        set vdd_y [$box yMax]
+        set vss_y [$box yMin]
+      }
+      MX {
+        set vdd_y [$box yMin]
+        set vss_y [$box yMax]
+      }
+      default {
+        utl::error "PDN" 25 "Unexpected row orientation $orient for row [$row getName]."
+      }
+    }
+
+    foreach lay [get_rails_layers] {
+      set xMin [$box xMin]
+      set xMax [$box xMax]
+      if {[is_extend_to_core_ring $lay]} {
+        # debug "Extending to core_ring - adjustment $ring_adjustment ($xMin/$xMax) ($stdcell_min_x/$stdcell_max_x)"
+        set voltage_domain [get_voltage_domain $xMin [$box yMin] $xMax [$box yMax]]
+        if {$voltage_domain == [dict get $design_data core_domain]} {
+          if {$xMin == $stdcell_min_x} {
+            set xMin [expr $xMin - $ring_adjustment]
+          }
+          if {$xMax == $stdcell_max_x} {
+            set xMax [expr $xMax + $ring_adjustment]
+          }
+        } else {
+          #Create lower metal followpin rails for voltage domains where the starting positions are not stdcell_min_x
+          set core_power [get_voltage_domain_power [dict get $design_data core_domain]]
+          set core_ground [get_voltage_domain_ground [dict get $design_data core_domain]]
+          set domain_power [get_voltage_domain_power $voltage_domain]
+          set domain_ground [get_voltage_domain_ground $voltage_domain]
+
+          set first_rect [lindex [[$block findRegion $voltage_domain] getBoundaries] 0]
+          set domain_xMin [$first_rect xMin]
+          set domain_xMax [$first_rect xMax]
+
+          if {$xMin == $domain_xMin} {
+            set xMin [expr $xMin - $ring_adjustment]
+          }
+          if {$xMax == $domain_xMax} {
+            set xMax [expr $xMax + $ring_adjustment]
+          }
+        }
+        # debug "Extended  to core_ring - adjustment $ring_adjustment ($xMin/$xMax)"
+      }
+      set width [dict get $grid_data rails $lay width]
+      # debug "VDD: $xMin [expr $vdd_y - $width / 2] $xMax [expr $vdd_y + $width / 2]"
+      set vdd_box [::odb::newSetFromRect $xMin [expr $vdd_y - $width / 2] $xMax [expr $vdd_y + $width / 2]]
+      set vdd_name [get_voltage_domain_power [get_voltage_domain $xMin [expr $vdd_y - $width / 2] $xMax [expr $vdd_y + $width / 2]]]
+      set vss_box [::odb::newSetFromRect $xMin [expr $vss_y - $width / 2] $xMax [expr $vss_y + $width / 2]]
+      set vss_name [get_voltage_domain_ground [get_voltage_domain $xMin [expr $vss_y - $width / 2] $xMax [expr $vss_y + $width / 2]]]
+      # generate power_rails using first domain_power
+      set first_power_name [lindex $vdd_name 0]
+      # debug "[$box xMin] [expr $vdd_y - $width / 2] [$box xMax] [expr $vdd_y + $width / 2]"
+      if {$first_power_name == [get_voltage_domain_power [dict get $design_data core_domain]]} {
+        add_stripe $lay "POWER" $vdd_box
+      } else {
+        add_stripe $lay "POWER_$first_power_name" $vdd_box
+      }
+      if {$vss_name == [get_voltage_domain_ground [dict get $design_data core_domain]]} {
+        add_stripe $lay "GROUND" $vss_box
+      } else {
+        add_stripe $lay "GROUND_$vss_name" $vss_box
+      }
+    }
+  }
+}
+
+proc starts_with {lay} {
+  variable grid_data
+  variable stripes_start_with
+
+  if {[dict exists $grid_data straps $lay starts_with]} {
+    set starts_with [dict get $grid_data straps $lay starts_with]
+  } elseif {[dict exists $grid_data starts_with]} {
+    set starts_with [dict get $grid_data starts_with]
+  } else {
+    set starts_with $stripes_start_with
+  }
+  return $starts_with
+}
+
+# proc for creating pdn mesh for upper metal layers
+proc generate_upper_metal_mesh_stripes {tag layer layer_info area} {
+# If the grid_data defines a spacing for the layer, then:
+#    place the second stripe spacing + width away from the first,
+# otherwise:
+#    place the second stripe pitch / 2 away from the first,
+#
+  set width [dict get $layer_info width]
+  set start_with [starts_with $layer]
+  # debug "Starts with: $start_with"
+
+  if {[get_dir $layer] == "hor"} {
+    set offset [expr [lindex $area 1] + [dict get $layer_info offset]]
+    if {![regexp "$start_with.*" $tag match]} { ;#If not starting from bottom with this net, 
+      if {[dict exists $layer_info spacing]} {
+        set offset [expr {$offset + [dict get $layer_info spacing] + [dict get $layer_info width]}]
+      } else {
+        set offset [expr {$offset + ([dict get $layer_info pitch] / 2)}]
+      }
+    }
+    for {set y $offset} {$y < [expr {[lindex $area 3] - [dict get $layer_info width]}]} {set y [expr {[dict get $layer_info pitch] + $y}]} {
+      set box [::odb::newSetFromRect [lindex $area 0] [expr $y - $width / 2] [lindex $area 2] [expr $y + $width / 2]]
+      add_stripe $layer $tag $box
+    }
+  } elseif {[get_dir $layer] == "ver"} {
+    set offset [expr [lindex $area 0] + [dict get $layer_info offset]]
+
+    if {![regexp "$start_with.*" $tag match]} { ;#If not starting from bottom with this net, 
+      if {[dict exists $layer_info spacing]} {
+        set offset [expr {$offset + [dict get $layer_info spacing] + [dict get $layer_info width]}]
+      } else {
+        set offset [expr {$offset + ([dict get $layer_info pitch] / 2)}]
+      }
+    }
+    for {set x $offset} {$x < [expr {[lindex $area 2] - [dict get $layer_info width]}]} {set x [expr {[dict get $layer_info pitch] + $x}]} {
+      set box [::odb::newSetFromRect [expr $x - $width / 2] [lindex $area 1] [expr $x + $width / 2] [lindex $area 3]]
+      add_stripe $layer $tag $box
+    }
+  } else {
+    utl::error "PDN" 26 "Invalid direction \"[get_dir $layer]\" for metal layer ${layer}. Should be either \"hor\" or \"ver\"."
+  }
+}
+
+proc adjust_area_for_core_rings {layer area number} {
+  variable grid_data
+
+  # When core_rings overlap with the stdcell area, we need to block out the area
+  # where the core rings have been placed.
+  if {[dict exists $grid_data core_ring_area $layer]} {
+    set core_ring_area [dict get $grid_data core_ring_area $layer]
+    set grid_area [odb::newSetFromRect {*}$area]
+    set grid_area [odb::subtractSet $grid_area $core_ring_area]
+    set area [get_extent $grid_area]
+  }
+
+  # Calculate how far to extend the grid to meet with the core rings
+  if {[dict exists $grid_data core_ring $layer pad_offset]} {
+    set pad_area [find_pad_offset_area]
+    set width [dict get $grid_data core_ring $layer width]
+    set offset [expr [dict get $grid_data core_ring $layer pad_offset]]
+    set spacing [dict get $grid_data core_ring $layer spacing]
+    set xMin [expr [lindex $pad_area 0] + $offset]
+    set yMin [expr [lindex $pad_area 1] + $offset]
+    set xMax [expr [lindex $pad_area 2] - $offset]
+    set yMax [expr [lindex $pad_area 3] - $offset]
+  } elseif {[dict exists $grid_data core_ring $layer core_offset]} {
+    set offset [dict get $grid_data core_ring $layer core_offset]
+    set width [dict get $grid_data core_ring $layer width]
+    set spacing [dict get $grid_data core_ring $layer spacing]
+    # debug "Area: $area"
+    # debug "Offset: $offset, Width $width, Spacing $spacing"
+
+    # The area figure includes a y offset for the width of the stdcell rail - so need to subtract it here
+    set rail_width [get_rails_max_width]
+    
+    # set extension area according to the number of power rings of the voltage domain, the default number is 2 
+    set xMin [expr [lindex $area 0] - $offset - $width - $spacing - $width / 2 - ($number - 2) * ($width + $spacing)]
+    set yMin [expr [lindex $area 1] - $offset - $width - $spacing - $width / 2 + $rail_width / 2 - ($number - 2) * ($width + $spacing)]
+    set xMax [expr [lindex $area 2] + $offset + $width + $spacing + $width / 2 + ($number - 2) * ($width + $spacing)]
+    set yMax [expr [lindex $area 3] + $offset + $width + $spacing + $width / 2 - $rail_width / 2 + ($number - 2) * ($width + $spacing)]
+  }
+  if {[get_dir $layer] == "hor"} {
+    set extended_area [list $xMin [lindex $area 1] $xMax [lindex $area 3]]
+  } else {
+    set extended_area [list [lindex $area 0] $yMin [lindex $area 2] $yMax]
+  }
+  return $extended_area
+}
+
+## this is a top-level proc to generate PDN stripes and insert vias between these stripes
+proc generate_stripes {tag net_name} {
+  variable plan_template
+  variable template
+  variable grid_data
+  variable block
+  variable design_data
+  variable voltage_domains
+
+  # debug "start: grid_name: [dict get $grid_data name]"
+  if {![dict exists $grid_data straps]} {return}
+  foreach lay [dict keys [dict get $grid_data straps]] {
+    # debug "    Layer $lay ..."
+    #Upper layer stripes
+    if {[dict exists $grid_data straps $lay width]} {
+      set area [dict get $grid_data area]
+      # debug "Area $area"
+      # Calculate the numebr of rings of core_domain
+      set ring_number 2 
+      if {[dict exists $grid_data core_ring] && [dict exists $grid_data core_ring $lay]} {
+        set area [adjust_area_for_core_rings $lay $area $ring_number]
+      }
+      # debug "area=$area (spec area=[dict get $grid_data area])"
+      # Create stripes for core domain's pwr/gnd nets
+      
+      if {$net_name == [get_voltage_domain_power [dict get $design_data core_domain]] ||
+          $net_name == [get_voltage_domain_ground [dict get $design_data core_domain]]} {
+        generate_upper_metal_mesh_stripes $tag $lay [dict get $grid_data straps $lay] $area
+        # Split core domains pwr/gnd nets when they cross other voltage domains that have different pwr/gnd nets
+        update_mesh_stripes_with_volatge_domains $tag $lay $net_name
+      }
+      # Create stripes for each voltage domains
+      foreach domain_name [dict keys $voltage_domains] {
+        if {$domain_name == [dict get $design_data core_domain]} {continue}
+        set domain [$block findRegion $domain_name]
+        set rect [lindex [$domain getBoundaries] 0]
+        set domain_name [$domain getName]
+        set domain_xMin [$rect xMin]
+        set domain_yMin [$rect yMin]
+        set domain_xMax [$rect xMax]
+        set domain_yMax [$rect yMax]
+        set width [dict get $grid_data core_ring $lay width]
+        set spacing [dict get $grid_data core_ring $lay spacing]
+        set rail_width [get_rails_max_width]
+        # Do not create duplicate stripes if the voltage domain has the same pwr/gnd nets as the core domain
+        if {($net_name == [get_voltage_domain_power $domain_name] && $net_name != [get_voltage_domain_power [dict get $design_data core_domain]]) ||
+             ($net_name == [get_voltage_domain_ground $domain_name] && $net_name != [get_voltage_domain_ground [dict get $design_data core_domain]])} {
+          set area [list $domain_xMin [expr $domain_yMin - $rail_width / 2] $domain_xMax [expr $domain_yMax + $rail_width / 2]]
+          set area [adjust_area_for_core_rings $lay $area 2]
+          set tag "$tag\_$net_name"
+          generate_upper_metal_mesh_stripes $tag $lay [dict get $grid_data straps $lay] $area
+        }
+        if {[lsearch -exact [get_voltage_domain_secondary_power $domain_name] $net_name] > -1} {
+          #Calculate the ring number of power_domain
+          set ring_number [lsearch -exact [get_voltage_domain_secondary_power $domain_name] $net_name]
+          set area [list [expr $domain_xMin + $ring_number * ($width + $spacing)] [expr $domain_yMin - $rail_width / 2] [expr $domain_xMax + $ring_number * ($width + $spacing)] [expr $domain_yMax + $rail_width / 2]]
+          set area [adjust_area_for_core_rings $lay $area [expr 3 + $ring_number]]
+          set tag "$tag\_$net_name"
+          generate_upper_metal_mesh_stripes $tag $lay [dict get $grid_data straps $lay] $area
+        }
+      }
+    } else {
+      foreach x [lsort -integer [dict keys $plan_template]] {
+        foreach y [lsort -integer [dict keys [dict get $plan_template $x]]] {
+          set template_name [dict get $plan_template $x $y]
+          set layer_info [dict get $grid_data straps $lay $template_name]
+          set area [list $x $y [expr $x + [dict get $template width]] [expr $y + [dict get $template height]]]
+          generate_upper_metal_mesh_stripes $tag $lay $layer_info $area
+        }
+      }
+    }
+  }
+}
+
+proc cut_blocked_areas {tag} {
+  variable stripe_locs
+  variable grid_data
+
+  if {![dict exists  $grid_data straps]} {return}
+
+  foreach layer_name [dict keys [dict get $grid_data straps]] {
+    set width [get_grid_wire_width $layer_name]
+
+    set blockages [get_blockages]
+    if {[dict exists $blockages $layer_name]} {
+      set stripe_locs($layer_name,$tag) [::odb::subtractSet $stripe_locs($layer_name,$tag) [dict get $blockages $layer_name]]
+
+      # Trim any shapes that are less than the width of the wire
+      set size_by [expr $width / 2 - 1]
+      set trimmed_set [::odb::shrinkSet $stripe_locs($layer_name,$tag) $size_by]
+      set stripe_locs($layer_name,$tag) [::odb::bloatSet $trimmed_set $size_by]
+    }
+  }
+}
+
+proc generate_grid_vias {tag net_name} {
+  variable vias
+  variable grid_data
+  variable design_data
+
+  if {$net_name != [get_voltage_domain_power [dict get $design_data core_domain]] &&
+      $net_name != [get_voltage_domain_ground [dict get $design_data core_domain]]} {
+    set tag "$tag\_$net_name"
+  }
+
+  #Via stacks
+  # debug "grid_data $grid_data"
+  if {[dict exists $grid_data connect]} {
+    # debug "Adding vias for $net_name ([llength [dict get $grid_data connect]] connections)..."
+    foreach connection [dict get $grid_data connect] {
+        set l1 [lindex $connection 0]
+        set l2 [lindex $connection 1]
+        # debug "    $l1 to $l2"
+        set connections [generate_via_stacks $l1 $l2 $tag $connection]
+        lappend vias [list net_name $net_name connections $connections]
+    }
+  }
+  # debug "End"
+}
+
+proc get_core_ring_centre {type side layer_info} {
+  variable grid_data
+
+  set spacing [dict get $layer_info spacing]
+  set width [dict get $layer_info width]
+
+  if {[dict exists $layer_info pad_offset]} {
+    set area [find_pad_offset_area]
+    lassign $area xMin yMin xMax yMax
+    set offset [expr [dict get $layer_info pad_offset] + $width / 2]
+    # debug "area        $area"
+    # debug "pad_offset  $offset"
+    # debug "spacing     $spacing"
+    # debug "width       $width"
+    switch $type {
+      "GROUND" {
+        switch $side {
+          "t" {return [expr $yMax - $offset]}
+          "b" {return [expr $yMin + $offset]}
+          "l" {return [expr $xMin + $offset]}
+          "r" {return [expr $xMax - $offset]}
+        }
+      }
+      "POWER" {
+        switch $side {
+          "t" {return [expr $yMax - $offset - $spacing - $width]}
+          "b" {return [expr $yMin + $offset + $spacing + $width]}
+          "l" {return [expr $xMin + $offset + $spacing + $width]}
+          "r" {return [expr $xMax - $offset - $spacing - $width]}
+        }
+      }
+    }
+  } elseif {[dict exists $layer_info core_offset]} {
+    set area [find_core_area]
+    set xMin [lindex $area 0]
+    set yMin [lindex $area 1]
+    set xMax [lindex $area 2]
+    set yMax [lindex $area 3]
+
+    set offset [dict get $layer_info core_offset]
+    # debug "area        $area"
+    # debug "core_offset $offset"
+    # debug "spacing     $spacing"
+    # debug "width       $width"
+    switch $type {
+      "POWER" {
+        switch $side {
+          "t" {return [expr $yMax + $offset]}
+          "b" {return [expr $yMin - $offset]}
+          "l" {return [expr $xMin - $offset]}
+          "r" {return [expr $xMax + $offset]}
+        }
+      }
+      "GROUND" {
+        switch $side {
+          "t" {return [expr $yMax + $offset + $spacing + $width]}
+          "b" {return [expr $yMin - $offset - $spacing - $width]}
+          "l" {return [expr $xMin - $offset - $spacing - $width]}
+          "r" {return [expr $xMax + $offset + $spacing + $width]}
+        }
+      }
+    }
+  }
+}
+
+proc real_value {value} {
+  variable def_units
+
+  return [expr $value * 1.0 / $def_units]
+}
+
+proc find_pad_offset_area {} {
+  variable block
+  variable grid_data
+  variable design_data
+
+  if {!([dict exists $grid_data pwr_pads] && [dict exists $grid_data gnd_pads])} {
+    utl::error "PDN" 48 "Need to define pwr_pads and gnd_pads in config file to use pad_offset option."
+  }
+
+  if {![dict exists $design_data config pad_offset_area]} {
+    set pad_names {}
+    dict for {pin_name pads} [dict get $grid_data pwr_pads] {
+      set pad_names [concat $pad_names $pads]
+    }
+    dict for {pin_name pads} [dict get $grid_data gnd_pads] {
+      set pad_names [concat $pad_names $pads]
+    }
+    set pad_names [lsort -unique $pad_names]
+    set die_area [dict get $design_data config die_area]
+    set xMin [lindex $die_area 0]
+    set yMin [lindex $die_area 1]
+    set xMax [lindex $die_area 2]
+    set yMax [lindex $die_area 3]
+
+    # debug "pad_names: $pad_names"
+    set found_b 0
+    set found_r 0
+    set found_t 0
+    set found_l 0
+    foreach inst [$block getInsts] {
+      if {[lsearch $pad_names [[$inst getMaster] getName]] > -1} {
+        # debug "inst_master: [[$inst getMaster] getName]"
+        set quadrant [get_design_quadrant {*}[$inst getOrigin]]
+        switch $quadrant {
+          "b" {
+            # debug "inst: [$inst getName], side: $quadrant, yMax: [real_value [[$inst getBBox] yMax]]"
+            set found_b 1
+            if {$yMin < [set y [[$inst getBBox] yMax]]} {
+              set yMin $y
+            }
+          }
+          "r" {
+            # debug "inst: [$inst getName], side: $quadrant, xMin: [real_value [[$inst getBBox] xMin]]"
+            set found_r 1
+            if {$xMax > [set x [[$inst getBBox] xMin]]} {
+              set xMax $x
+            }
+          }
+          "t" {
+            # debug "inst: [$inst getName], side: $quadrant, yMin: [real_value [[$inst getBBox] yMin]]"
+            set found_t 1
+            if {$yMax > [set y [[$inst getBBox] yMin]]} {
+              set yMax $y
+            }
+          }
+          "l" {
+            # debug "inst: [$inst getName], side: $quadrant, xMax: [real_value [[$inst getBBox] xMax]]"
+            set found_l 1
+            if {$xMin < [set x [[$inst getBBox] xMax]]} {
+              set xMin $x
+            }
+          }
+        }
+      }
+    }
+    if {$found_b == 0} {
+      utl::warn "PDN" 64 "No power/ground pads found on bottom edge."
+    }
+    if {$found_r == 0} {
+      utl::warn "PDN" 65 "No power/ground pads found on right edge."
+    }
+    if {$found_t == 0} {
+      utl::warn "PDN" 66 "No power/ground pads found on top edge."
+    }
+    if {$found_l == 0} {
+      utl::warn "PDN" 67 "No power/ground pads found on left edge."
+    }
+    if {$found_b == 0 || $found_r == 0 || $found_t == 0 || $found_l == 0} {
+      utl::error "PDN" 68 "Cannot place core rings without pwr/gnd pads on each side."
+    }
+    # debug "pad_area: ([real_value $xMin] [real_value $yMin]) ([real_value $xMax] [real_value $yMax])"
+    dict set design_data config pad_offset_area [list $xMin $yMin $xMax $yMax]
+  }
+
+  return [dict get $design_data config pad_offset_area]
+}
+
+proc generate_core_rings {core_ring_data} {
+  variable grid_data
+
+  dict for {layer layer_info} $core_ring_data {
+    if {[dict exists $layer_info pad_offset]} {
+      set area [find_pad_offset_area]
+      set offset [expr [dict get $layer_info pad_offset] + [dict get $layer_info width] / 2]
+
+      set xMin [lindex $area 0]
+      set yMin [lindex $area 1]
+      set xMax [lindex $area 2]
+      set yMax [lindex $area 3]
+
+      set spacing [dict get $layer_info spacing]
+      set width [dict get $layer_info width]
+
+      set outer_lx [expr $xMin + $offset]
+      set outer_ly [expr $yMin + $offset]
+      set outer_ux [expr $xMax - $offset]
+      set outer_uy [expr $yMax - $offset]
+
+      set inner_lx [expr $xMin + $offset + $spacing + $width]
+      set inner_ly [expr $yMin + $offset + $spacing + $width]
+      set inner_ux [expr $xMax - $offset - $spacing - $width]
+      set inner_uy [expr $yMax - $offset - $spacing - $width]
+    } elseif {[dict exists $layer_info core_offset]} {
+
+      set area [list {*}[[ord::get_db_core] ll] {*}[[ord::get_db_core] ur]]
+      set offset [dict get $layer_info core_offset]
+
+      set xMin [lindex $area 0]
+      set yMin [lindex $area 1]
+      set xMax [lindex $area 2]
+      set yMax [lindex $area 3]
+
+      set spacing [dict get $layer_info spacing]
+      set width [dict get $layer_info width]
+
+      set inner_lx [expr $xMin - $offset]
+      set inner_ly [expr $yMin - $offset]
+      set inner_ux [expr $xMax + $offset]
+      set inner_uy [expr $yMax + $offset]
+
+      set outer_lx [expr $xMin - $offset - $spacing - $width]
+      set outer_ly [expr $yMin - $offset - $spacing - $width]
+      set outer_ux [expr $xMax + $offset + $spacing + $width]
+      set outer_uy [expr $yMax + $offset + $spacing + $width]
+    }
+
+    if {[get_dir $layer] == "hor"} {
+      set lower_power \
+        [odb::newSetFromRect \
+          [expr $inner_lx - $width / 2] \
+          [expr $inner_ly - $width / 2] \
+          [expr $inner_ux + $width / 2] \
+          [expr $inner_ly + $width / 2] \
+        ]
+      
+      set upper_power \
+        [odb::newSetFromRect \
+          [expr $inner_lx - $width / 2] \
+          [expr $inner_uy - $width / 2] \
+          [expr $inner_ux + $width / 2] \
+          [expr $inner_uy + $width / 2] \
+        ]
+
+      set lower_ground \
+        [odb::newSetFromRect \
+          [expr $outer_lx - $width / 2] \
+          [expr $outer_ly - $width / 2] \
+          [expr $outer_ux + $width / 2] \
+          [expr $outer_ly + $width / 2] \
+        ]
+      set upper_ground \
+        [odb::newSetFromRect \
+          [expr $outer_lx - $width / 2] \
+          [expr $outer_uy - $width / 2] \
+          [expr $outer_ux + $width / 2] \
+          [expr $outer_uy + $width / 2] \
+        ]
+ 
+      add_stripe $layer "POWER" $upper_power
+      add_stripe $layer "POWER" $lower_power
+      add_stripe $layer "GROUND" $upper_ground
+      add_stripe $layer "GROUND" $lower_ground
+
+      set core_rings [odb::orSets [list \
+        [odb::newSetFromRect [expr $outer_lx - $width / 2] [expr $outer_ly - $width / 2] [expr $outer_ux + $width / 2] [expr $inner_ly + $width / 2]] \
+        [odb::newSetFromRect [expr $outer_lx - $width / 2] [expr $inner_uy - $width / 2] [expr $outer_ux + $width / 2] [expr $outer_uy + $width / 2]] \
+      ]]
+
+      set core_ring_area [odb::bloatSet $core_rings $spacing]
+      dict set grid_data core_ring_area $layer $core_ring_area
+
+    } else {
+      set lhs_power \
+        [odb::newSetFromRect \
+          [expr $inner_lx - $width / 2] \
+          [expr $inner_ly - $width / 2] \
+          [expr $inner_lx + $width / 2] \
+          [expr $inner_uy + $width / 2] \
+        ]
+      set rhs_power \
+        [odb::newSetFromRect \
+          [expr $inner_ux - $width / 2] \
+          [expr $inner_ly - $width / 2] \
+          [expr $inner_ux + $width / 2] \
+          [expr $inner_uy + $width / 2] \
+        ]
+
+      set lhs_ground \
+        [odb::newSetFromRect \
+          [expr $outer_lx - $width / 2] \
+          [expr $outer_ly - $width / 2] \
+          [expr $outer_lx + $width / 2] \
+          [expr $outer_uy + $width / 2] \
+        ]
+      set rhs_ground \
+        [odb::newSetFromRect \
+          [expr $outer_ux - $width / 2] \
+          [expr $outer_ly - $width / 2] \
+          [expr $outer_ux + $width / 2] \
+          [expr $outer_uy + $width / 2] \
+        ]
+
+      add_stripe $layer "POWER" $lhs_power
+      add_stripe $layer "POWER" $rhs_power
+      add_stripe $layer "GROUND" $lhs_ground
+      add_stripe $layer "GROUND" $rhs_ground
+      
+      set core_rings [odb::orSets [list \
+        [odb::newSetFromRect [expr $outer_lx - $width / 2] [expr $outer_ly - $width / 2] [expr $inner_lx + $width / 2] [expr $outer_uy + $width / 2]] \
+        [odb::newSetFromRect [expr $inner_ux - $width / 2] [expr $outer_ly - $width / 2] [expr $outer_ux + $width / 2] [expr $outer_uy + $width / 2]] \
+      ]]
+     
+      set core_ring_area [odb::bloatSet $core_rings $spacing]
+      dict set grid_data core_ring_area $layer $core_ring_area
+
+    }
+  }
+  set ring_areas {}
+  foreach layer [dict keys [dict get $grid_data core_ring_area]] {
+    lappend ring_areas [dict get $grid_data core_ring_area $layer]
+  }
+  dict set grid_data core_ring_area combined [odb::orSets $ring_areas]
+}
+
+proc get_macro_boundaries {} {
+  variable instances
+
+  set boundaries {}
+  foreach instance [dict keys $instances] {
+    lappend boundaries [dict get $instances $instance macro_boundary]
+  }
+
+  return $boundaries
+}
+
+proc get_stdcell_specification {} {
+  variable design_data
+
+  if {[dict exists $design_data grid stdcell]} {
+    set grid_name [lindex [dict keys [dict get $design_data grid stdcell]] 0]
+    return [dict get $design_data grid stdcell $grid_name]
+  } else {
+    if {![dict exists $design_data grid stdcell]} {
+      utl::error "PDN" 17 "No stdcell grid specification found - no rails can be inserted."
+    }
+  }
+
+  return {}
+}
+
+proc get_rail_width {} {
+  variable default_grid_data
+
+  set max_width 0
+  foreach layer [get_rails_layers] {
+    set max_width [expr max($max_width,[get_grid_wire_width $layer])]
+  }
+  if {![dict exists $default_grid_data units]} {
+    set max_width [ord::microns_to_dbu $max_width]
+  }
+  return $max_width
+}
+
+
+proc get_macro_blocks {} {
+  variable macros
+
+  if {[llength $macros] > 0} {return $macros}
+
+  # debug "start"
+  foreach lib [[ord::get_db] getLibs] {
+    foreach cell [$lib getMasters] {
+      if {![$cell isBlock] && ![$cell isPad]} {continue}
+      set macro_name [$cell getName]
+      dict set macros $macro_name width  [$cell getWidth]
+      dict set macros $macro_name height [$cell getHeight]
+
+      set blockage_layers {}
+      foreach obs [$cell getObstructions] {
+        set layer_name [[$obs getTechLayer] getName]
+        dict set blockage_layers $layer_name 1
+      }
+      dict set macros $macro_name blockage_layers [dict keys $blockage_layers]
+
+      set pin_layers {}
+      set power_pins {}
+      set ground_pins {}
+      set first_shape 1
+
+      foreach term [$cell getMTerms] {
+        set sig_type [$term getSigType]
+        if {$sig_type == "POWER"} {
+          lappend power_pins [$term getName]
+        } elseif {$sig_type == "GROUND"} {
+          lappend ground_pins [$term getName]
+        } else {
+          continue
+        }
+        
+        foreach pin [$term getMPins] {
+          foreach shape [$pin getGeometry] {
+            lappend pin_layers [[$shape getTechLayer] getName]
+            if {$first_shape == 1} {
+              set xMin [$shape xMin]
+              set xMax [$shape xMax]
+              set yMin [$shape yMin]
+              set yMax [$shape yMax]
+              set first_shape 0
+            } else {
+              set xMin [expr min($xMin,[$shape xMin])]
+              set xMax [expr max($xMax,[$shape xMax])]
+              set yMin [expr min($yMin,[$shape yMin])]
+              set yMax [expr max($yMax,[$shape yMax])]
+            }
+          }
+        }
+      }
+
+      dict set macros $macro_name pin_layers [lsort -unique $pin_layers]
+      dict set macros $macro_name power_pins [lsort -unique $power_pins]
+      dict set macros $macro_name ground_pins [lsort -unique $ground_pins]
+      if {$first_shape == 0}  {
+        dict set macros $macro_name pins_area [list $xMin $yMin $xMax $yMax]
+      } else {
+        dict set macros $macro_name pins_area [list 0 0 0 0]
+      }
+    }
+  }
+
+  return $macros
+}
+
+proc filtered_insts_within {instances boundary} {
+  set filtered_instances {}
+  dict for {instance_name instance} $instances {
+    # If there are no shapes left after 'and'ing the boundard with the cell, then
+    # the cell lies outside the area where we are adding a power grid.
+    set llx [dict get $instance xmin]
+    set lly [dict get $instance ymin]
+    set urx [dict get $instance xmax]
+    set ury [dict get $instance ymax]
+
+    set box [odb::newSetFromRect $llx $lly $urx $ury]
+    if {[llength [odb::getPolygons [odb::andSet $boundary $box]]] != 0} {
+      dict set filtered_instances $instance_name $instance
+    }
+  }
+  return $filtered_instances
+}
+
+proc import_macro_boundaries {} {
+  variable libs
+  variable instances
+
+  set macros [get_macro_blocks]
+  set instances [find_instances_of [dict keys $macros]]
+
+  # debug "end"
+}
+
+proc get_instances {} {
+  variable instances
+
+  if {[llength $instances] > 0} {return $instances}
+
+  set block [ord::get_db_block]
+  foreach inst [$block getInsts] {
+    if {![[$inst getMaster] isBlock] && ![[$inst getMaster] isPad]} {continue}
+    set instance {}
+    dict set instance name [$inst getName]
+    dict set instance inst $inst
+    dict set instance macro [[$inst getMaster] getName]
+    dict set instance x [lindex [$inst getOrigin] 0]
+    dict set instance y [lindex [$inst getOrigin] 1]
+    dict set instance xmin [[$inst getBBox] xMin]
+    dict set instance ymin [[$inst getBBox] yMin]
+    dict set instance xmax [[$inst getBBox] xMax]
+    dict set instance ymax [[$inst getBBox] yMax]
+    dict set instance orient [$inst getOrient]
+
+
+    set llx [dict get $instance xmin]
+    set lly [dict get $instance ymin]
+    set urx [dict get $instance xmax]
+    set ury [dict get $instance ymax]
+    dict set instance macro_boundary [list $llx $lly $urx $ury]
+    dict set instances [$inst getName] $instance
+
+    set_instance_halo [$inst getName] [get_default_halo]
+  }
+
+  return $instances
+}
+
+proc get_master_pg_pins_area {macro_name} {
+  variable macros
+
+  return [dict get $macros $macro_name pins_area]
+}
+
+proc get_instance_pg_pins_area {inst_name} {
+  variable instances
+
+  set instance [dict get $instances $inst_name]
+  set inst [dict get $instance inst]
+
+  set master_area [transform_box {*}[get_master_pg_pins_area [[$inst getMaster] getName]] [$inst getOrigin] [$inst getOrient]]
+}
+
+proc set_instance_halo {inst_name halo} {
+  variable instances 
+
+  set instance [dict get $instances $inst_name]
+  set inst [dict get $instance inst]
+
+  if {[$inst getHalo] != "NULL"} {
+    set halo [list \
+      [[$inst getHalo] xMin] \
+      [[$inst getHalo] yMin] \
+      [[$inst getHalo] xMax] \
+      [[$inst getHalo] yMax] \
+    ]
+  }
+  dict set instances $inst_name halo $halo
+  # debug "Inst: [$inst getName], halo: [dict get $instances $inst_name halo]"
+
+  set llx [expr round([dict get $instance xmin] - [lindex $halo 0])]
+  set lly [expr round([dict get $instance ymin] - ([lindex $halo 1] - [get_rail_width] / 2))]
+  set urx [expr round([dict get $instance xmax] + [lindex $halo 2])]
+  set ury [expr round([dict get $instance ymax] + ([lindex $halo 3] - [get_rail_width] / 2))]
+
+  dict set instances $inst_name halo_boundary [list $llx $lly $urx $ury]
+}
+
+proc find_instances_of {macro_names} {
+  variable design_data
+  variable macros
+
+  set selected_instances {}
+
+  dict for {inst_name instance} [get_instances] {
+    set macro_name [dict get $instance macro]
+    if {[lsearch -exact $macro_names $macro_name] == -1} {continue}
+    dict set selected_instances $inst_name $instance
+  }
+
+  return $selected_instances
+}
+
+proc export_opendb_vias {} {
+  variable physical_viarules
+  variable block
+  variable tech
+  # debug "[llength $physical_viarules]"
+  dict for {name rules} $physical_viarules {
+    foreach rule $rules {
+      # Dont create illegal vias
+      if {[dict exists $rule illegal]} {continue}
+      if {[dict exists $rule fixed]} {continue}
+
+      # debug "$rule"
+      set via [$block findVia [dict get $rule name]]
+      if {$via == "NULL"} {
+        set via [odb::dbVia_create $block [dict get $rule name]]
+        # debug "Via $via"
+
+        $via setViaGenerateRule [$tech findViaGenerateRule [dict get $rule rule]]
+        set params [$via getViaParams]
+        $params setBottomLayer [$tech findLayer [lindex [dict get $rule layers] 0]]
+        $params setCutLayer [$tech findLayer [lindex [dict get $rule layers] 1]]
+        $params setTopLayer [$tech findLayer [lindex [dict get $rule layers] 2]]
+        $params setXCutSize [lindex [dict get $rule cutsize] 0]
+        $params setYCutSize [lindex [dict get $rule cutsize] 1]
+        $params setXCutSpacing [lindex [dict get $rule cutspacing] 0]
+        $params setYCutSpacing [lindex [dict get $rule cutspacing] 1]
+        $params setXBottomEnclosure [lindex [dict get $rule enclosure] 0]
+        $params setYBottomEnclosure [lindex [dict get $rule enclosure] 1]
+        $params setXTopEnclosure [lindex [dict get $rule enclosure] 2]
+        $params setYTopEnclosure [lindex [dict get $rule enclosure] 3]
+        $params setNumCutRows [lindex [dict get $rule rowcol] 0]
+        $params setNumCutCols [lindex [dict get $rule rowcol] 1]
+
+        $via setViaParams $params
+      }
+    }
+  }
+  # debug "end"
+}
+
+proc get_global_connect_list_default {voltage_domain is_region} {
+  variable block
+  variable voltage_domains
+
+  foreach net_type "primary_power primary_ground" {
+    set net_name [dict get $voltage_domains $voltage_domain $net_type]
+    foreach sub_net $net_name {
+      set net [$block findNet $sub_net]
+      foreach term [get_valid_mterms $sub_net] {
+        if {$is_region} {
+          pdn::add_global_connect $block $voltage_domain ".*" $term $net
+        } else {
+          pdn::add_global_connect ".*" $term $net
+        }
+      }
+    }
+  }
+}
+
+proc get_global_connect_list {net_name} {
+  variable design_data
+  variable global_connections
+  variable voltage_domains
+
+  set connect_patterns {}
+  if {[dict exist $global_connections $net_name]} {
+    foreach pattern [dict get $global_connections $net_name] {
+      lappend connect_patterns $pattern
+    }
+  }
+
+  return $connect_patterns
+}
+
+proc export_opendb_global_connection {} {
+  variable block
+  variable design_data
+  variable global_connections
+  variable voltage_domains
+
+  ## Do global connect statements first
+  get_global_connect_list_default [dict get $design_data core_domain] false
+  
+  foreach net_type "power_nets ground_nets" {
+    foreach net_name [dict get $design_data $net_type] {
+      set net [$block findNet $net_name]
+      foreach pattern [get_global_connect_list $net_name] {
+        pdn::add_global_connect [dict get $pattern inst_name] [dict get $pattern pin_name] $net
+      }
+    }
+  }
+
+  ## Do regions second
+  set core_domain_name [dict get $design_data core_domain]
+  foreach voltage_domain [dict keys $voltage_domains] {
+    if {$voltage_domain != $core_domain_name} {
+      get_global_connect_list_default $voltage_domain true
+
+      foreach {net_type netname} [dict get $voltage_domains $voltage_domain] {
+        set net [$block findNet $net_name]
+        # loop over all patterns
+        foreach pattern [get_global_connect_list $net_name] {
+          pdn::add_global_connect $block $voltage_domain [dict get $pattern inst_name] [dict get $pattern pin_name] $net
+        }
+      }
+    }
+  }
+
+  pdn::global_connect $block
+}
+
+proc export_opendb_specialnet {net_name signal_type} {
+  variable block
+  variable instances
+  variable metal_layers
+  variable tech
+  variable stripe_locs
+  variable global_connections
+  variable design_data
+
+  set net [$block findNet $net_name]
+  if {$net == "NULL"} {
+    set net [odb::dbNet_create $block $net_name]
+  }
+  $net setSpecial
+  $net setSigType $signal_type
+  # debug "net $net_name. signaltype, $signal_type, global_connections: $global_connections"
+
+  if {[check_snet_is_unique $net]} {
+    $net setWildConnected
+  }
+  set swire [odb::dbSWire_create $net "ROUTED"]
+  if {$net_name != [get_voltage_domain_power [dict get $design_data core_domain]] &&
+      $net_name != [get_voltage_domain_ground [dict get $design_data core_domain]]} {
+    set signal_type "$signal_type\_$net_name"
+  }
+
+  # debug "layers - $metal_layers"
+  foreach lay $metal_layers {
+    if {[array names stripe_locs "$lay,$signal_type"] == ""} {continue}
+
+    set layer [find_layer $lay]
+    foreach rect [::odb::getRectangles $stripe_locs($lay,$signal_type)] {
+      set xMin [$rect xMin]
+      set xMax [$rect xMax]
+      set yMin [$rect yMin]
+      set yMax [$rect yMax]
+
+      set width [expr $xMax - $xMin]
+      set height [expr $yMax - $yMin]
+
+      set wire_type "STRIPE"
+      if {[is_rails_layer $lay]} {set wire_type "FOLLOWPIN"}
+      # debug "$xMin $yMin $xMax $yMax $wire_type"
+      odb::dbSBox_create $swire $layer $xMin $yMin $xMax $yMax $wire_type
+    }
+  }
+
+  variable vias
+  # debug "vias - [llength $vias]"
+  foreach via $vias {
+    if {[dict get $via net_name] == $net_name} {
+      # For each layer between l1 and l2, add vias at the intersection
+      foreach via_inst [dict get $via connections] {
+        # debug "$via_inst"
+        set via_name [dict get $via_inst name]
+        set x        [dict get $via_inst x]
+        set y        [dict get $via_inst y]
+        # debug "$via_name $x $y [$block findVia $via_name]"
+        if {[set defvia [$block findVia $via_name]] != "NULL"} {
+          odb::dbSBox_create $swire $defvia $x $y "STRIPE"
+        } elseif {[set techvia [$tech findVia $via_name]] != "NULL"} {
+          odb::dbSBox_create $swire $techvia $x $y "STRIPE"
+        } else {
+          utl::error "PDN" 69 "Cannot find via $via_name."
+        }
+        # debug "via created"
+      }
+    }
+  }
+  # debug "end"
+}
+
+proc export_opendb_specialnets {} {
+  variable block
+  variable design_data
+
+  foreach net_name [dict get $design_data power_nets] {
+    export_opendb_specialnet $net_name "POWER"
+  }
+
+  foreach net_name [dict get $design_data ground_nets] {
+    export_opendb_specialnet $net_name "GROUND"
+  }
+
+  export_opendb_global_connection
+}
+
+proc export_opendb_power_pin {net_name signal_type} {
+  variable metal_layers
+  variable block
+  variable stripe_locs
+  variable tech
+  variable voltage_domains
+  variable design_data
+
+  if {![dict exists $design_data grid stdcell]} {return}
+
+  set pins_layers {}
+  dict for {grid_name grid} [dict get $design_data grid stdcell] {
+    if {[dict exists $grid pins]} {
+      lappend pins_layers {*}[dict get $grid pins]
+    }
+  }
+  set pins_layers [lsort -unique $pins_layers]
+  if {[llength $pins_layers] == 0} {return}
+
+  set net [$block findNet $net_name]
+  if {$net == "NULL"} {
+    utl::error PDN 70 "Cannot find net $net_name in the design."
+  }
+  set bterms [$net getBTerms]
+  if {[llength $bterms] < 1} {
+    set bterm [odb::dbBTerm_create $net "${net_name}"]
+    if {$bterm == "NULL"} {
+      utl::error PDN 71 "Cannot create terminal for net $net_name."
+    }
+  }
+  # debug $bterm
+  foreach bterm [$net getBTerms] {
+    $bterm setSigType $signal_type
+  }
+  set bterm [lindex [$net getBTerms] 0]
+  set bpin [odb::dbBPin_create $bterm]
+  $bpin setPlacementStatus "FIRM"
+
+  dict for {domain domain_info} $voltage_domains {
+    if {$domain != [dict get $design_data core_domain] &&
+        $net_name == [dict get $domain_info primary_power]} {
+      set r_pin "r_$net_name"
+      set r_net [odb::dbNet_create $block $r_pin]
+      set r_bterm [odb::dbBTerm_create $r_net "${r_pin}"]
+
+      set r_bpin [odb::dbBPin_create $r_bterm]
+      $r_bpin setPlacementStatus "FIRM"
+    }
+  }
+
+  if {$net_name != [get_voltage_domain_power [dict get $design_data core_domain]] &&
+      $net_name != [get_voltage_domain_ground [dict get $design_data core_domain]]} {
+    set signal_type "$signal_type\_$net_name"
+  }
+
+  foreach lay [lreverse $metal_layers] {
+    if {[array names stripe_locs "$lay,$signal_type"] == "" ||
+        [lsearch -exact $pins_layers $lay] == -1} {continue}
+    foreach shape [::odb::getPolygons $stripe_locs($lay,$signal_type)] {
+      set points [::odb::getPoints $shape]
+      if {[llength $points] != 4} {
+        # We already issued a message for this - no need to repeat
+        continue
+      }
+      set xMin [expr min([[lindex $points 0] getX], [[lindex $points 1] getX], [[lindex $points 2] getX], [[lindex $points 3] getX])]
+      set xMax [expr max([[lindex $points 0] getX], [[lindex $points 1] getX], [[lindex $points 2] getX], [[lindex $points 3] getX])]
+      set yMin [expr min([[lindex $points 0] getY], [[lindex $points 1] getY], [[lindex $points 2] getY], [[lindex $points 3] getY])]
+      set yMax [expr max([[lindex $points 0] getY], [[lindex $points 1] getY], [[lindex $points 2] getY], [[lindex $points 3] getY])]
+
+      set layer [$tech findLayer $lay]
+      odb::dbBox_create $bpin $layer $xMin $yMin $xMax $yMax
+      if {[info exists r_bpin]} {
+        odb::dbBox_create $r_bpin $layer $xMin $yMin $xMax $yMax
+      }
+
+    }
+  }
+}
+
+proc export_opendb_power_pins {} {
+  variable block
+  variable design_data
+
+  foreach net_name [dict get $design_data power_nets] {
+    export_opendb_power_pin $net_name "POWER"
+  }
+
+  foreach net_name [dict get $design_data ground_nets] {
+    export_opendb_power_pin $net_name "GROUND"
+  }
+
+}
+
+## procedure for file existence check, returns 0 if file does not exist or file exists, but empty
+proc file_exists_non_empty {filename} {
+  return [expr [file exists $filename] && [file size $filename] > 0]
+}
+
+proc get {args} {
+  variable design_data
+
+  return [dict get $design_data {*}$args]
+}
+proc get_macro_power_pins {inst_name} {
+  set specification [select_instance_specification $inst_name]
+  if {[dict exists $specification power_pins]} {
+    return [dict get $specification power_pins]
+  }
+  return "VDDPE VDDCE"
+}
+proc get_macro_ground_pins {inst_name} {
+  set specification [select_instance_specification $inst_name]
+  if {[dict exists $specification ground_pins]} {
+    return [dict get $specification ground_pins]
+  }
+  return "VSSE"
+}
+
+proc transform_box {xmin ymin xmax ymax origin orientation} {
+  switch -exact $orientation {
+    R0    {set new_box [list $xmin $ymin $xmax $ymax]}
+    R90   {set new_box [list [expr -1 * $ymax] $xmin [expr -1 * $ymin] $xmax]}
+    R180  {set new_box [list [expr -1 * $xmax] [expr -1 * $ymax] [expr -1 * $xmin] [expr -1 * $ymin]]}
+    R270  {set new_box [list $ymin [expr -1 * $xmax] $ymax [expr -1 * $xmin]]}
+    MX    {set new_box [list $xmin [expr -1 * $ymax] $xmax [expr -1 * $ymin]]}
+    MY    {set new_box [list [expr -1 * $xmax] $ymin [expr -1 * $xmin] $ymax]}
+    MXR90 {set new_box [list $ymin $xmin $ymax $xmax]}
+    MYR90 {set new_box [list [expr -1 * $ymax] [expr -1 * $xmax] [expr -1 * $ymin] [expr -1 * $xmin]]}
+    default {utl::error "PDN" 27 "Illegal orientation $orientation specified."}
+  }
+  return [list \
+    [expr [lindex $new_box 0] + [lindex $origin 0]] \
+    [expr [lindex $new_box 1] + [lindex $origin 1]] \
+    [expr [lindex $new_box 2] + [lindex $origin 0]] \
+    [expr [lindex $new_box 3] + [lindex $origin 1]] \
+  ]
+}
+
+proc set_template_size {width height} {
+  variable template
+  variable def_units
+
+  dict set template width [expr round($width * $def_units)]
+  dict set template height [expr round($height * $def_units)]
+}
+
+proc get_memory_instance_pg_pins {} {
+  variable block
+  variable metal_layers
+
+  # debug "start"
+  set boundary [odb::newSetFromRect {*}[get_core_area]]
+
+  foreach inst [$block getInsts] {
+    set inst_name [$inst getName]
+    set master [$inst getMaster]
+
+    if {![$master isBlock]} {continue}
+
+    # If there are no shapes left after 'and'ing the boundard with the cell, then
+    # the cell lies outside the area where we are adding a power grid.
+    set bbox [$inst getBBox]
+    set box [odb::newSetFromRect [$bbox xMin] [$bbox yMin] [$bbox xMax] [$bbox yMax]]
+    if {[llength [odb::getPolygons [odb::andSet $boundary $box]]] == 0} {
+      # debug "Instance [$inst getName] does not lie in the cell area"
+      continue
+    }
+
+    # debug "cell name - [$master getName]"
+
+    foreach term_name [concat [get_macro_power_pins $inst_name] [get_macro_ground_pins $inst_name]] {
+      set master [$inst getMaster]
+      set mterm [$master findMTerm $term_name]
+      if {$mterm == "NULL"} {
+        utl::warn "PDN" 37 "Cannot find pin $term_name on instance [$inst getName] ([[$inst getMaster] getName])."
+        continue
+      }
+
+      set type [$mterm getSigType]
+      foreach mPin [$mterm getMPins] {
+        foreach geom [$mPin getGeometry] {
+          set layer [[$geom getTechLayer] getName]
+          if {[lsearch -exact $metal_layers $layer] == -1} {continue}
+
+          set box [transform_box [$geom xMin] [$geom yMin] [$geom xMax] [$geom yMax] [$inst getOrigin] [$inst getOrient]]
+
+          set width  [expr abs([lindex $box 2] - [lindex $box 0])]
+          set height [expr abs([lindex $box 3] - [lindex $box 1])]
+
+          if {$width > $height} {
+            set layer_name ${layer}_PIN_hor
+          } else {
+            set layer_name ${layer}_PIN_ver
+          }
+          # debug "Adding pin for [$inst getName]:[$mterm getName] to layer $layer_name ($box)"
+          add_stripe $layer_name $type [odb::newSetFromRect {*}$box]
+        }
+      }
+    }
+  }
+  # debug "Total walltime till macro pin geometry creation = [expr {[expr {[clock clicks -milliseconds] - $::start_time}]/1000.0}] seconds"
+  # debug "end"
+}
+
+proc set_core_area {xmin ymin xmax ymax} {
+  variable design_data
+
+  dict set design_data config core_area [list $xmin $ymin $xmax $ymax]
+}
+
+proc get_core_area {} {
+  variable design_data
+
+  return [get_extent [get_stdcell_area]]
+}
+
+proc write_pdn_strategy {} {
+  variable design_data
+
+  if {[dict exists $design_data grid]} {
+    set_pdn_string_property_value "strategy" [dict get $design_data grid]
+  }
+
+}
+
+proc init_tech {} {
+  variable db
+  variable block
+  variable tech
+  variable libs
+  variable def_units
+
+  set db [ord::get_db]
+  set tech [ord::get_db_tech]
+  set libs [$db getLibs]
+  set block [ord::get_db_block]
+
+  set def_units [$block getDefUnits]
+
+  init_metal_layers
+  init_via_tech
+
+}
+
+proc add_power_net {net_name} {
+  variable power_nets
+
+  if {[lsearch -exact $power_nets $net_name] == -1} {
+    lappend power_nets $net_name
+  }
+}
+
+proc add_ground_net {net_name} {
+  variable ground_nets
+
+  if {[lsearch -exact $ground_nets $net_name] == -1} {
+    lappend ground_nets $net_name
+  }
+}
+
+proc get_default_halo {} {
+  if {[info vars ::halo] != ""} {
+    if {[llength $::halo] == 1} {
+      set default_halo "$::halo $::halo $::halo $::halo"
+    } elseif {[llength $::halo] == 2} {
+      set default_halo "$::halo $::halo"
+    } elseif {[llength $::halo] == 4} {
+      set default_halo $::halo
+    } else {
+      utl::error "PDN" 29 "Illegal number of elements defined for ::halo \"$::halo\" (1, 2 or 4 allowed)."
+    }
+  } else {
+    set default_halo "0 0 0 0"
+  }
+  return [lmap x $default_halo {ord::microns_to_dbu $x}]
+}
+
+proc get_row_height {} {
+  set first_row [lindex [[ord::get_db_block] getRows] 0]
+  set row_site [$first_row getSite]
+
+  return [$row_site getHeight]
+}
+
+proc init {args} {
+  variable db
+  variable block
+  variable tech
+  variable libs
+  variable design_data
+  variable def_output
+  variable default_grid_data
+  variable design_name
+  variable stripe_locs
+  variable site
+  variable row_height
+  variable metal_layers
+  variable def_units
+  variable stripes_start_with
+  variable physical_viarules
+  variable stdcell_area
+  variable voltage_domains
+  variable global_connections
+  variable default_global_connections
+  variable power_nets
+  variable ground_nets
+
+  # debug "start" 
+  init_tech 
+
+  set design_name [$block getName]
+
+  set physical_viarules {}
+  set stdcell_area ""
+ 
+  set die_area [$block getDieArea]
+
+  utl::info "PDN" 8 "Design name is $design_name."
+  set def_output "${design_name}_pdn.def"
+
+  # debug "examine vars"
+  if {$power_nets == {}} {
+    if {[info vars ::power_nets] == ""} {
+      set power_nets "VDD"
+    } else {
+      set power_nets $::power_nets
+    }
+  }
+  
+  if {$ground_nets == {}} {
+    if {[info vars ::ground_nets] == ""} {
+      set ground_nets "VSS"
+    } else {
+      set ground_nets $::ground_nets
+    }
+  }
+
+  if {[info vars ::core_domain] == ""} {
+    set core_domain "CORE"
+    if {![dict exists $voltage_domains $core_domain primary_power]} {
+      dict set voltage_domains $core_domain primary_power [lindex $power_nets 0]
+    }
+    if {![dict exists $voltage_domains $core_domain primary_ground]} {
+      dict set voltage_domains $core_domain primary_ground [lindex $ground_nets 0]
+    }
+  } else {
+    set core_domain $::core_domain
+  }
+
+  if {[info vars ::stripes_start_with] == ""} {
+    set stripes_start_with "GROUND"
+  } else {
+    set stripes_start_with $::stripes_start_with
+  }
+  
+  dict set design_data power_nets $power_nets
+  dict set design_data ground_nets $ground_nets
+  dict set design_data core_domain $core_domain
+
+  # Sourcing user inputs file
+  #
+  set row_height [get_row_height]
+
+  ##### Get information from BEOL LEF
+  utl::info "PDN" 9 "Reading technology data."
+
+  if {[info vars ::layers] != ""} {
+    foreach layer $::layers {
+      if {[dict exists $::layers $layer widthtable]} {
+        dict set ::layers $layer widthtable [lmap x [dict get $::layers $layer widthtable] {expr $x * $def_units}]
+      }
+    }
+    set_layer_info $::layers
+  }
+
+  dict set design_data config def_output   $def_output
+  dict set design_data config design       $design_name
+  dict set design_data config die_area     [list [$die_area xMin]  [$die_area yMin] [$die_area xMax] [$die_area yMax]]
+
+  array unset stripe_locs
+
+  ########################################
+  # Remove existing power/ground nets
+  #######################################
+  foreach pg_net [concat [dict get $design_data power_nets] [dict get $design_data ground_nets]] {
+    set net [$block findNet $pg_net]
+    if {$net != "NULL"} {
+      foreach swire [$net getSWires] {
+        odb::dbSWire_destroy $swire
+      }
+    }
+  }
+
+  # debug "Set the core area"
+  # Set the core area
+  if {[info vars ::core_area_llx] != "" && [info vars ::core_area_lly] != "" && [info vars ::core_area_urx] != "" && [info vars ::core_area_ury] != ""} {
+     # The core area is larger than the stdcell area by half a rail, since the stdcell rails extend beyond the rails
+     set_core_area \
+       [expr round($::core_area_llx * $def_units)] \
+       [expr round($::core_area_lly * $def_units)] \
+       [expr round($::core_area_urx * $def_units)] \
+       [expr round($::core_area_ury * $def_units)]
+  } else {
+    set_core_area {*}[get_extent [get_stdcell_plus_area]]
+  }
+
+  ##### Basic sanity checks to see if inputs are given correctly
+  foreach layer [get_rails_layers] {
+    if {[lsearch -exact $metal_layers $layer] < 0} {
+      utl::error "PDN" 30 "Layer specified for stdcell rails '$layer' not in list of layers."
+    }
+  }
+  # debug "end"
+
+  return $design_data
+}
+
+proc convert_layer_spec_to_def_units {data} {
+  foreach key {width pitch spacing offset pad_offset core_offset} {
+    if {[dict exists $data $key]} {
+      dict set data $key [ord::microns_to_dbu [dict get $data $key]]
+    }
+  }
+  return $data
+}
+
+proc specify_grid {type specification} {
+  if {![dict exists $specification type]} {
+    dict set specification type $type
+  }
+  verify_grid $specification
+}
+
+proc get_quadrant {rect x y} {
+  set dw [expr [lindex $rect 2] - [lindex $rect 0]]
+  set dh [expr [lindex $rect 3] - [lindex $rect 1]]
+
+  set test_x [expr $x - [lindex $rect 0]]
+  set test_y [expr $y - [lindex $rect 1]]
+  # debug "$dw * $test_y ([expr $dw * $test_y]) > expr $dh * $test_x ([expr $dh * $test_x])"
+  if {$dw * $test_y > $dh * $test_x} {
+    # Top or left
+    if {($dw * $test_y) + ($dh * $test_x) > ($dw * $dh)} {
+      # Top or right
+      return "t"
+    } else {
+      # Bottom or left
+      return "l"
+    }
+  } else {
+    # Bottom or right
+    if {($dw * $test_y) + ($dh * $test_x) > ($dw * $dh)} {
+      # Top or right
+      return "r"
+    } else {
+      # Bottom or left
+      return "b"
+    }
+  }
+}
+
+proc get_design_quadrant {x y} {
+  variable design_data
+
+  set die_area [dict get $design_data config die_area]
+  return [get_quadrant $die_area $x $y]
+}
+
+proc get_core_facing_pins {instance pin_name side layer} {
+  variable block
+  set geoms {}
+  set core_pins {}
+  set inst [$block findInst [dict get $instance name]]
+  if {[set iterm [$inst findITerm $pin_name]] == "NULL"} {
+    utl::warn "PDN" 55 "Cannot find pin $pin_name on inst [$inst getName]."
+    return {}
+  }
+  if {[set mterm [$iterm getMTerm]] == "NULL"} {
+    utl::warn "PDN" 56 "Cannot find master pin $pin_name for cell [[$inst getMaster] getName]."
+    return {}
+  }
+  set pins [$mterm getMPins]
+
+  # debug "start"
+  foreach pin $pins {
+    foreach geom [$pin getGeometry] {
+      if {[[$geom getTechLayer] getName] != $layer} {continue}
+      lappend geoms $geom
+    }
+  }
+  # debug "$pins"
+  foreach geom $geoms {
+    set ipin [transform_box [$geom xMin] [$geom yMin] [$geom xMax] [$geom yMax] [$inst getOrigin] [$inst getOrient]]
+    # debug "$ipin [[$inst getBBox] xMin] [[$inst getBBox] yMin] [[$inst getBBox] xMax] [[$inst getBBox] yMax] "
+    switch $side {
+      "t" {
+        if {[lindex $ipin 1] == [[$inst getBBox] yMin]} {
+          lappend core_pins [list \
+            centre [expr ([lindex $ipin 2] + [lindex $ipin 0]) / 2] \
+            width [expr [lindex $ipin 2] - [lindex $ipin 0]] \
+          ]
+        }
+      }
+      "b" {
+        if {[lindex $ipin 3] == [[$inst getBBox] yMax]} {
+          lappend core_pins [list \
+            centre [expr ([lindex $ipin 2] + [lindex $ipin 0]) / 2] \
+            width [expr [lindex $ipin 2] - [lindex $ipin 0]] \
+          ]
+        }
+      }
+      "l" {
+        if {[lindex $ipin 2] == [[$inst getBBox] xMax]} {
+          lappend core_pins [list \
+            centre [expr ([lindex $ipin 3] + [lindex $ipin 1]) / 2] \
+            width [expr [lindex $ipin 3] - [lindex $ipin 1]] \
+          ]
+        }
+      }
+      "r" {
+        if {[lindex $ipin 0] == [[$inst getBBox] xMin]} {
+          lappend core_pins [list \
+            centre [expr ([lindex $ipin 3] + [lindex $ipin 1]) / 2] \
+            width [expr [lindex $ipin 3] - [lindex $ipin 1]] \
+          ]
+        }
+      }
+    }
+  }
+  # debug "$core_pins"
+  return $core_pins
+}
+
+proc connect_pads_to_core_ring {type pin_name pads} {
+  variable grid_data
+  variable pad_cell_blockages
+
+  dict for {inst_name instance} [find_instances_of $pads] {
+    set side [get_design_quadrant [dict get $instance x] [dict get $instance y]]
+    switch $side {
+      "t" {
+        set required_direction "ver"
+      }
+      "b" {
+        set required_direction "ver"
+      }
+      "l" {
+        set required_direction "hor"
+      }
+      "r" {
+        set required_direction "hor"
+      }
+    }
+    foreach non_pref_layer [dict keys [dict get $grid_data core_ring]] {
+      if {[get_dir $non_pref_layer] != $required_direction} {
+        set non_pref_layer_info [dict get $grid_data core_ring $non_pref_layer]
+        break
+      }
+    }
+    # debug "find_layer"
+    foreach pref_layer [dict keys [dict get $grid_data core_ring]] {
+      if {[get_dir $pref_layer] == $required_direction} {
+        break
+      }
+    }
+    switch $side {
+      "t" {
+        set y_min [expr [get_core_ring_centre $type $side $non_pref_layer_info] - [dict get $grid_data core_ring $non_pref_layer width] / 2]
+        set y_min_blk [expr $y_min - [dict get $grid_data core_ring $non_pref_layer spacing]]
+        set y_max [dict get $instance ymin]
+        # debug "t: [dict get $instance xmin] $y_min_blk [dict get $instance xmax] [dict get $instance ymax]"
+        add_padcell_blockage $pref_layer [odb::newSetFromRect [dict get $instance xmin] $y_min_blk [dict get $instance xmax] [dict get $instance ymax]]
+      }
+      "b" {
+        # debug "[get_core_ring_centre $type $side $non_pref_layer_info] + [dict get $grid_data core_ring $non_pref_layer width] / 2"
+        set y_max [expr [get_core_ring_centre $type $side $non_pref_layer_info] + [dict get $grid_data core_ring $non_pref_layer width] / 2]
+        set y_max_blk [expr $y_max + [dict get $grid_data core_ring $non_pref_layer spacing]]
+        set y_min [dict get $instance ymax]
+        # debug "b: [dict get $instance xmin] [dict get $instance ymin] [dict get $instance xmax] $y_max"
+        add_padcell_blockage $pref_layer [odb::newSetFromRect [dict get $instance xmin] [dict get $instance ymin] [dict get $instance xmax] $y_max_blk]
+        # debug "end b"
+      }
+      "l" {
+        set x_max [expr [get_core_ring_centre $type $side $non_pref_layer_info] + [dict get $grid_data core_ring $non_pref_layer width] / 2]
+        set x_max_blk [expr $x_max + [dict get $grid_data core_ring $non_pref_layer spacing]]
+        set x_min [dict get $instance xmax]
+        # debug "l: [dict get $instance xmin] [dict get $instance ymin] $x_max [dict get $instance ymax]"
+        add_padcell_blockage $pref_layer [odb::newSetFromRect [dict get $instance xmin] [dict get $instance ymin] $x_max_blk [dict get $instance ymax]]
+      }
+      "r" {
+        set x_min [expr [get_core_ring_centre $type $side $non_pref_layer_info] - [dict get $grid_data core_ring $non_pref_layer width] / 2]
+        set x_min_blk [expr $x_min - [dict get $grid_data core_ring $non_pref_layer spacing]]
+        set x_max [dict get $instance xmin]
+        # debug "r: $x_min_blk [dict get $instance ymin] [dict get $instance xmax] [dict get $instance ymax]"
+        add_padcell_blockage $pref_layer [odb::newSetFromRect $x_min_blk [dict get $instance ymin] [dict get $instance xmax] [dict get $instance ymax]]
+      }
+    }
+
+    # debug "$pref_layer"
+    foreach pin_geometry [get_core_facing_pins $instance $pin_name $side $pref_layer] {
+      set centre [dict get $pin_geometry centre]
+      set width  [dict get $pin_geometry width]
+
+      variable tech
+      if {[[set layer [$tech findLayer $pref_layer]] getMaxWidth] != "NULL" && $width > [$layer getMaxWidth]} {
+        set width [$layer getMaxWidth]
+      }
+      if {$required_direction == "hor"} {
+        # debug "added_strap $pref_layer $type $x_min [expr $centre - $width / 2] $x_max [expr $centre + $width / 2]"
+        add_stripe $pref_layer "PAD_$type" [odb::newSetFromRect $x_min [expr $centre - $width / 2] $x_max [expr $centre + $width / 2]]
+      } else {
+        # debug "added_strap $pref_layer $type [expr $centre - $width / 2] $y_min [expr $centre + $width / 2] $y_max"
+        add_stripe $pref_layer "PAD_$type" [odb::newSetFromRect [expr $centre - $width / 2] $y_min [expr $centre + $width / 2] $y_max]
+      }
+    }
+  }
+  # debug "end"
+}
+
+proc add_pad_straps {tag} {
+  variable stripe_locs
+
+  foreach pad_connection [array names stripe_locs "*,PAD_*"] {
+    if {![regexp "(.*),PAD_$tag" $pad_connection - layer]} {continue}
+    # debug "$pad_connection"
+    if {[array names stripe_locs "$layer,$tag"] != ""} {
+      # debug add_pad_straps "Before: $layer [llength [::odb::getPolygons $stripe_locs($layer,$tag)]]"
+      # debug add_pad_straps "Adding: [llength [::odb::getPolygons $stripe_locs($pad_connection)]]"
+      add_stripe $layer $tag $stripe_locs($pad_connection)
+      # debug add_pad_straps "After:  $layer [llength [::odb::getPolygons $stripe_locs($layer,$tag)]]"
+    }
+  }
+}
+
+proc print_spacing_table {layer_name} {
+  set layer [find_layer $layer_name]
+  if {[$layer hasTwoWidthsSpacingRules]} {
+    set table_size [$layer getTwoWidthsSpacingTableNumWidths]
+    for {set i 0} {$i < $table_size} {incr i} {
+      set width [$layer getTwoWidthsSpacingTableWidth $i]
+      set report_width "WIDTH $width"
+      if {[$layer getTwoWidthsSpacingTableHasPRL $i]} {
+        set prl [$layer getTwoWidthsSpacingTablePRL $i]
+        set report_prl " PRL $prl"
+      } else {
+        set report_prl ""
+      }
+      set report_spacing " [$layer getTwoWidthsSpacingTableEntry 0 $i] "
+    }
+    utl::report "${report_width}${report_prl}${report_spacing}"
+  }
+}
+
+proc get_twowidths_table {table_type} {
+  variable metal_layers
+  set twowidths_table {}
+
+  foreach layer_name $metal_layers {
+    set spacing_table [get_spacingtables $layer_name]
+    set prls {}
+
+    if {[dict exists $spacing_table TWOWIDTHS $table_type]} {
+      set layer_spacing_table [dict get $spacing_table TWOWIDTHS $table_type]
+      set table_size [dict size $layer_spacing_table]
+      set table_widths [dict keys $layer_spacing_table]
+
+      for {set i 0} {$i < $table_size} {incr i} {
+
+        set width [lindex $table_widths $i]
+        set spacing [lindex [dict get $layer_spacing_table $width spacings] $i]
+
+        if {[dict get $layer_spacing_table $width prl] != 0} {
+          set prl [dict get $layer_spacing_table $width prl]
+          set update_prls {}
+          dict for {prl_entry prl_setting} $prls {
+            if {$prl <= [lindex $prl_entry 0]} {break}
+            dict set update_prls $prl_entry $prl_setting
+            dict set twowidths_table $layer_name $width $prl_entry $prl_setting
+          }
+          dict set update_prls $prl $spacing
+          dict set twowidths_table $layer_name $width $prl $spacing
+          set prls $update_prls
+        } else {
+          set prls {}
+          dict set prls 0 $spacing
+          dict set twowidths_table $layer_name $width 0 $spacing
+        }
+      }
+    }
+  }
+
+  return $twowidths_table
+}
+
+proc get_twowidths_tables {} {
+  variable twowidths_table
+  variable twowidths_table_wrongdirection
+
+  set twowidths_table [get_twowidths_table NONE]
+  set twowidths_table_wrongdirection [get_twowidths_table WRONGDIRECTION]
+}
+
+proc select_from_table {table width} {
+  foreach value [lreverse [lsort -integer [dict keys $table]]] {
+    if {$width > $value} {
+      return $value
+    }
+  }
+  return [lindex [dict keys $table] 0]
+}
+
+proc get_preferred_direction_spacing {layer_name width prl} {
+  variable twowidths_table
+
+  # debug "$layer_name $width $prl"
+  # debug "twowidths_table $twowidths_table"
+  if {$twowidths_table == {}} {
+    return [[find_layer $layer_name] getSpacing]
+  } else {
+    set width_key [select_from_table [dict get $twowidths_table $layer_name] $width]
+    set prl_key   [select_from_table [dict get $twowidths_table $layer_name $width_key] $prl]
+  }
+
+  return [dict get $twowidths_table $layer_name $width_key $prl_key]
+}
+
+proc get_nonpreferred_direction_spacing {layer_name width prl} {
+  variable twowidths_table_wrongdirection
+
+  # debug "twowidths_table_wrong_direction $twowidths_table_wrongdirection"
+  if {[dict exists $twowidths_table_wrongdirection $layer_name]} {
+    set width_key [select_from_table [dict get $twowidths_table_wrongdirection $layer_name] $width]
+    set prl_key   [select_from_table [dict get $twowidths_table_wrongdirection $layer_name $width_key] $prl]
+  } else {
+    return [get_preferred_direction_spacing $layer_name $width $prl]
+  }
+
+  return [dict get $twowidths_table_wrongdirection $layer_name $width_key $prl_key]
+}
+
+proc create_obstructions {layer_name polygons} {
+  set layer [find_layer $layer_name]
+  set min_spacing [get_preferred_direction_spacing $layer_name 0 0]
+
+  # debug "Num polygons [llength $polygons]"
+
+  foreach polygon $polygons {
+    set points [::odb::getPoints $polygon]
+    if {[llength $points] != 4} {
+      utl::warn "PDN" 6 "Unexpected number of points in stripe of $layer_name."
+      continue
+    }
+    set xMin [expr min([[lindex $points 0] getX], [[lindex $points 1] getX], [[lindex $points 2] getX], [[lindex $points 3] getX])]
+    set xMax [expr max([[lindex $points 0] getX], [[lindex $points 1] getX], [[lindex $points 2] getX], [[lindex $points 3] getX])]
+    set yMin [expr min([[lindex $points 0] getY], [[lindex $points 1] getY], [[lindex $points 2] getY], [[lindex $points 3] getY])]
+    set yMax [expr max([[lindex $points 0] getY], [[lindex $points 1] getY], [[lindex $points 2] getY], [[lindex $points 3] getY])]
+
+    if {[get_dir $layer_name] == "hor"} {
+      set required_spacing_pref    [get_preferred_direction_spacing $layer_name [expr $yMax - $yMin] [expr $xMax - $xMin]]
+      set required_spacing_nonpref [get_nonpreferred_direction_spacing $layer_name [expr $xMax - $xMin] [expr $yMax - $yMin]]
+
+      set y_change [expr $required_spacing_pref    - $min_spacing]
+      set x_change [expr $required_spacing_nonpref - $min_spacing]
+    } else {
+      set required_spacing_pref    [get_preferred_direction_spacing $layer_name [expr $xMax - $xMin] [expr $yMax - $yMin]]
+      set required_spacing_nonpref [get_nonpreferred_direction_spacing $layer_name [expr $yMax - $yMin] [expr $xMax - $xMin]]
+
+      set x_change [expr $required_spacing_pref    - $min_spacing]
+      set y_change [expr $required_spacing_nonpref - $min_spacing]
+    }
+
+    create_obstruction_object_blockage $layer $min_spacing [expr $xMin - $x_change] [expr $yMin - $y_change] [expr $xMax + $x_change] [expr $yMax + $y_change]
+  }
+}
+
+proc combine {lside rside} {
+  # debug "l [llength $lside] r [llength $rside]"
+  if {[llength $lside] > 1} {
+    set lside [combine [lrange $lside 0 [expr [llength $lside] / 2 - 1]] [lrange $lside [expr [llength $lside] / 2] end]]
+  }
+  if {[llength $rside] > 1} {
+    set rside [combine [lrange $rside 0 [expr [llength $rside] / 2 - 1]] [lrange $rside [expr [llength $rside] / 2] end]]
+  }
+  return [odb::orSet $lside $rside]
+}
+
+proc shapes_to_polygonSet {shapes} {
+  if {[llength $shapes] == 1} {
+    return $shapes
+  }
+  return [combine [lrange $shapes 0 [expr [llength $shapes] / 2 - 1]] [lrange $shapes [expr [llength $shapes] / 2] end]]
+}
+
+proc generate_obstructions {layer_name} {
+  variable stripe_locs
+
+  # debug "layer $layer_name"
+  get_twowidths_tables
+
+  set block_shapes {}
+  foreach tag {"POWER" "GROUND"} {
+    if {[array names stripe_locs $layer_name,$tag] == ""} {
+      # debug "No polygons on $layer_name,$tag"
+      continue
+    }
+    if {$block_shapes == {}} {
+      set block_shapes $stripe_locs($layer_name,$tag)
+    } else {
+      set block_shapes [odb::orSet $block_shapes $stripe_locs($layer_name,$tag)]
+    }
+  }
+  set via_shapes 0
+  variable vias
+  # debug "vias - [llength $vias]"
+  foreach via $vias {
+    # For each layer between l1 and l2, add vias at the intersection
+    foreach via_inst [dict get $via connections] {
+      # debug "$via_inst"
+      set via_name [dict get $via_inst name]
+      set x        [dict get $via_inst x]
+      set y        [dict get $via_inst y]
+
+      set lower_layer_name [lindex [dict get $via_inst layers] 0]
+      set upper_layer_name [lindex [dict get $via_inst layers] 2]
+
+      if {$lower_layer_name == $layer_name && [dict exists $via_inst lower_rect]} {
+        lappend block_shapes [odb::newSetFromRect {*}[transform_box {*}[dict get $via_inst lower_rect] [list $x $y] "R0"]]
+        incr via_shapes
+      } elseif {$upper_layer_name == $layer_name && [dict exists $via_inst upper_rect]} {
+        lappend block_shapes [odb::newSetFromRect {*}[transform_box {*}[dict get $via_inst upper_rect] [list $x $y] "R0"]]
+        incr via_shapes
+      }
+    }
+  }
+  # debug "Via shapes $layer_name $via_shapes"
+  if {$block_shapes != {}} {
+  # debug "create_obstructions [llength $block_shapes]"
+    create_obstructions $layer_name [odb::getPolygons [shapes_to_polygonSet $block_shapes]]
+  }
+  # debug "end"
+}
+
+proc create_obstruction_object_blockage {layer min_spacing xMin yMin xMax yMax} {
+  variable block
+
+
+  set layer_pitch [get_pitch $layer]
+  set layer_width [$layer getWidth]
+  # debug "Layer - [$layer getName], pitch $layer_pitch, width $layer_width"
+  set tracks [$block findTrackGrid $layer]
+  set offsetX [lindex [$tracks getGridX] 0]
+  set offsetY [lindex [$tracks getGridY] 0]
+
+  # debug "OBS: [$layer getName] $xMin $yMin $xMax $yMax (dx [expr $xMax - $xMin] dy [expr $yMax - $yMin])"
+  # debug "Offsets: x $offsetX y $offsetY"
+  set relative_xMin [expr $xMin - $offsetX]
+  set relative_xMax [expr $xMax - $offsetX]
+  set relative_yMin [expr $yMin - $offsetY]
+  set relative_yMax [expr $yMax - $offsetY]
+  # debug "relative to core area $relative_xMin $relative_yMin $relative_xMax $relative_yMax"
+
+  # debug "OBS: [$layer getName] $xMin $yMin $xMax $yMax"
+  # Determine which tracks are blocked
+  if {[get_dir [$layer getName]] == "hor"} {
+    set pitch_start [expr $relative_yMin / $layer_pitch]
+    if {$relative_yMin % $layer_pitch >= ($min_spacing + $layer_width / 2)} {
+      incr pitch_start
+    }
+    set pitch_end [expr $relative_yMax / $layer_pitch]
+    if {$relative_yMax % $layer_pitch > $layer_width / 2} {
+      incr pitch_end
+    }
+    # debug "pitch: start $pitch_start end $pitch_end"
+    for {set i $pitch_start} {$i <= $pitch_end} {incr i} {
+      set obs [odb::dbObstruction_create $block $layer \
+        $xMin \
+        [expr $i * $layer_pitch + $offsetY - $layer_width / 2] \
+        $xMax \
+        [expr $i * $layer_pitch + $offsetY + $layer_width / 2] \
+      ]
+    }
+  } else {
+    set pitch_start [expr $relative_xMin / $layer_pitch]
+    if {$relative_xMin % $layer_pitch >= ($min_spacing + $layer_width / 2)} {
+      incr pitch_start
+    }
+    set pitch_end [expr $relative_xMax / $layer_pitch]
+    if {$relative_xMax % $layer_pitch > $layer_width / 2} {
+      incr pitch_end
+    }
+    # debug "pitch: start $pitch_start end $pitch_end"
+    for {set i $pitch_start} {$i <= $pitch_end} {incr i} {
+      set obs [odb::dbObstruction_create $block $layer \
+        [expr $i * $layer_pitch + $offsetX - $layer_width / 2] \
+        $yMin \
+        [expr $i * $layer_pitch + $offsetX + $layer_width / 2] \
+        $yMax \
+      ]
+    }
+  }
+}
+
+proc create_obstruction_object_net {layer min_spacing xMin yMin xMax yMax} {
+  variable block
+  variable obstruction_index
+
+  incr obstruction_index
+  set net_name "obstruction_$obstruction_index"
+  if {[set obs_net [$block findNet $net_name]] == "NULL"} {
+    set obs_net [odb::dbNet_create $block $net_name]
+  }
+  # debug "obs_net [$obs_net getName]"
+  if {[set wire [$obs_net getWire]] == "NULL"} {
+    set wire [odb::dbWire_create $obs_net]
+  }
+  # debug "Wire - net [[$wire getNet] getName]"
+  set encoder [odb::dbWireEncoder]
+  $encoder begin $wire
+
+  set layer_pitch [$layer getPitch]
+  set layer_width [$layer getWidth]
+  # debug "Layer - [$layer getName], pitch $layer_pitch, width $layer_width"
+  set core_area [get_core_area]
+  # debug "core_area $core_area"
+  set relative_xMin [expr $xMin - [lindex $core_area 0]]
+  set relative_xMax [expr $xMax - [lindex $core_area 0]]
+  set relative_yMin [expr $yMin - [lindex $core_area 1]]
+  set relative_yMax [expr $yMax - [lindex $core_area 1]]
+  # debug "relative to core area $relative_xMin $relative_yMin $relative_xMax $relative_yMax"
+
+  # debug "OBS: [$layer getName] $xMin $yMin $xMax $yMax"
+  # Determine which tracks are blocked
+  if {[get_dir [$layer getName]] == "hor"} {
+    set pitch_start [expr $relative_yMin / $layer_pitch]
+    if {$relative_yMin % $layer_pitch > ($min_spacing + $layer_width / 2)} {
+      incr pitch_start
+    }
+    set pitch_end [expr $relative_yMax / $layer_pitch]
+    if {$relative_yMax % $layer_pitch > $layer_width / 2} {
+      incr pitch_end
+    }
+    for {set i $pitch_start} {$i <= $pitch_end} {incr i} {
+      $encoder newPath $layer ROUTED
+      $encoder addPoint [expr $relative_xMin + [lindex $core_area 0]] [expr $i * $layer_pitch + [lindex $core_area 1]]
+      $encoder addPoint [expr $relative_xMax + [lindex $core_area 0]] [expr $i * $layer_pitch + [lindex $core_area 1]]
+    }
+  } else {
+    set pitch_start [expr $relative_xMin / $layer_pitch]
+    if {$relative_xMin % $layer_pitch > ($min_spacing + $layer_width / 2)} {
+      incr pitch_start
+    }
+    set pitch_end [expr $relative_xMax / $layer_pitch]
+    if {$relative_xMax % $layer_pitch > $layer_width / 2} {
+      incr pitch_end
+    }
+    for {set i $pitch_start} {$i <= $pitch_end} {incr i} {
+      $encoder newPath $layer ROUTED
+      $encoder addPoint [expr $i * $layer_pitch + [lindex $core_area 0]] [expr $relative_yMin + [lindex $core_area 1]]
+      $encoder addPoint [expr $i * $layer_pitch + [lindex $core_area 0]] [expr $relative_yMax + [lindex $core_area 1]]
+    }
+  }
+  $encoder end
+}
+
+proc add_grid {} {
+  variable design_data
+  variable grid_data
+
+  if {[dict exists $grid_data core_ring]} {
+    set area [dict get $grid_data area]
+    # debug "Area $area"
+    
+    generate_core_rings [dict get $grid_data core_ring]
+    
+    if {[dict exists $grid_data gnd_pads]} {
+      dict for {pin_name cells} [dict get $grid_data gnd_pads] {
+        connect_pads_to_core_ring "GROUND" $pin_name $cells
+      }
+    }
+    if {[dict exists $grid_data pwr_pads]} {
+      dict for {pin_name cells} [dict get $grid_data pwr_pads] {
+        connect_pads_to_core_ring "POWER" $pin_name $cells
+      }
+    }
+    
+    generate_voltage_domain_rings [dict get $grid_data core_ring]
+    # merge_stripes
+    # set intersections [odb::andSet $stripe_locs(G1,POWER) $stripe_locs(G2,POWER)]
+    # debug "# intersections [llength [odb::getPolygons $intersections]]"
+    # foreach pwr_net [dict get $design_data power_nets] {
+    #   generate_grid_vias "POWER" $pwr_net
+    # }
+    # foreach gnd_net [dict get $design_data ground_nets] {
+    #   generate_grid_vias "GROUND" $gnd_net
+    # }
+    apply_padcell_blockages
+  }
+
+  set area [dict get $grid_data area]
+
+  if {[dict exists $grid_data rails]} {
+    # debug "Adding stdcell rails"
+    # debug "area: [dict get $grid_data area]"
+    set area [dict get $grid_data area]
+    # debug "Area $area"
+    generate_lower_metal_followpin_rails
+  }
+
+  ## Power nets
+  foreach pwr_net [dict get $design_data power_nets] {
+    set tag "POWER"
+    generate_stripes $tag $pwr_net
+  }
+
+  ## Ground nets
+  foreach gnd_net [dict get $design_data ground_nets] {
+    set tag "GROUND"
+    generate_stripes $tag $gnd_net
+  }
+
+  merge_stripes
+
+  ## Power nets
+  # debug "Power straps"
+  foreach pwr_net [dict get $design_data power_nets] {
+    set tag "POWER"
+    cut_blocked_areas $tag
+    add_pad_straps $tag
+  }
+
+  ## Ground nets
+  # debug "Ground straps"
+  foreach gnd_net [dict get $design_data ground_nets] {
+    set tag "GROUND"
+    cut_blocked_areas $tag
+    add_pad_straps $tag
+  }
+  merge_stripes
+
+  if {[dict exists $grid_data obstructions]} {
+    utl::info "PDN" 32 "Generating blockages for TritonRoute."
+    # debug "Obstructions: [dict get $grid_data obstructions]"
+    foreach layer_name [dict get $grid_data obstructions] {
+      generate_obstructions $layer_name
+    }
+  }
+  # debug "end"
+}
+
+proc select_instance_specification {instance} {
+  variable design_data
+  variable instances
+
+  if {![dict exists $instances $instance grid]} {
+    utl::error PAD 248 "Instance $instance is not associated with any grid"
+  }
+  return [dict get $design_data grid macro [dict get $instances $instance grid]]
+}
+
+proc get_instance_specification {instance} {
+  variable instances
+
+  set specification [select_instance_specification $instance]
+
+  if {![dict exists $specification blockages]} {
+    dict set specification blockages {}
+  }
+  dict set specification area [dict get $instances $instance macro_boundary]
+
+  return $specification
+}
+
+proc get_pitch {layer} {
+  if {[$layer hasXYPitch]} {
+    if {[get_dir [$layer getName]] == "hor"} {
+      return [$layer getPitchY]
+    } else {
+      return [$layer getPitchX]
+    }
+  } else {
+    return [$layer getPitch]
+  }
+}
+
+proc get_layer_number {layer_name} {
+  set layer [[ord::get_db_tech] findLayer $layer_name]
+  if {$layer == "NULL"} {
+    utl::error PDN 160 "Cannot find layer $layer_name."
+  }
+  return [$layer getNumber]
+}
+
+proc init_metal_layers {} {
+  variable metal_layers
+  variable layers
+  variable block
+
+  set tech [ord::get_db_tech]
+  set block [ord::get_db_block]
+  set metal_layers {}
+
+  foreach layer [$tech getLayers] {
+    if {[$layer getType] == "ROUTING"} {
+      set_prop_lines $layer LEF58_TYPE
+      # Layers that have LEF58_TYPE are not normal ROUTING layers, so should not be considered
+      if {![empty_propline]} {continue}
+
+      set layer_name [$layer getName]
+      lappend metal_layers $layer_name
+
+      # debug "Direction ($layer_name): [$layer getDirection]"
+      if {[$layer getDirection] == "HORIZONTAL"} {
+        dict set layers $layer_name direction "hor"
+      } else {
+        dict set layers $layer_name direction "ver"
+      }
+      dict set layers $layer_name pitch [get_pitch $layer]
+
+      set tracks [$block findTrackGrid $layer]
+      if {$tracks == "NULL"} {
+        utl::warn "PDN" 35 "No track information found for layer $layer_name."
+      } else {
+        dict set layers $layer_name offsetX [lindex [$tracks getGridX] 0]
+        dict set layers $layer_name offsetY [lindex [$tracks getGridY] 0]
+      }
+    }
+  }
+}
+
+proc get_instance_llx {instance} {
+  variable instances
+  return [lindex [dict get $instances $instance halo_boundary] 0]
+}
+
+proc get_instance_lly {instance} {
+  variable instances
+  return [lindex [dict get $instances $instance halo_boundary] 1]
+}
+
+proc get_instance_urx {instance} {
+  variable instances
+  return [lindex [dict get $instances $instance halo_boundary] 2]
+}
+
+proc get_instance_ury {instance} {
+  variable instances
+  return [lindex [dict get $instances $instance halo_boundary] 3]
+}
+
+proc get_macro_blockage_layers {instance} {
+  variable metal_layers
+
+  set specification [select_instance_specification $instance]
+  if {[dict exists $specification blockages]} {
+    # debug "Block [dict get $specification blockages] for $instance"
+    return [dict get $specification blockages]
+  }
+  return $metal_layers
+}
+
+proc report_layer_details {layer} {
+  variable def_units
+
+  set str " - "
+  foreach element {width pitch spacing offset pad_offset core_offset} {
+    if {[dict exists $layer $element]} {
+      set str [format "$str $element: %.3f " [expr 1.0 * [dict get $layer $element] / $def_units]]
+    }
+  }
+  return $str
+}
+
+proc print_strategy {type specification} {
+  if {[dict exists $specification name]} {
+    set header "Type: ${type}, [dict get $specification name]"
+  } else {
+    set header "Type: $type"
+  }
+
+  if {$type == "macro"} {
+    if {[dict exists $specification grid_over_pg_pins]} {
+      if {[dict get $specification grid_over_pg_pins] == 1} {
+        set header "$header -grid_over_pg_pins"
+      } else {
+        set header "$header -grid_over_boundary"
+      }
+    }
+  }
+
+  utl::report $header
+
+  if {[dict exists $specification core_ring]} {
+    utl::report "    Core Rings"
+    dict for {layer_name layer} [dict get $specification core_ring] {
+      set str "      Layer: $layer_name"
+      if {[dict exists $layer width]} {
+        set str "$str [report_layer_details $layer]"
+        utl::report $str
+      } else {
+        utl::report $str
+        foreach template [dict keys $layer] {
+          utl::report -nonewline [format "          %-14s %s" $template [report_layer_details [dict get $layer $template]]]
+        }
+      }
+    }
+  }
+  if {[dict exists $specification rails]} {
+    utl::report "    Stdcell Rails"
+    dict for {layer_name layer} [dict get $specification rails] {
+      if {[dict exists $layer width]} {
+        utl::report "      Layer: $layer_name [report_layer_details $layer]"
+      } else {
+        utl::report "      Layer: $layer_name"
+        foreach template [dict keys $layer] {
+          utl::report [format "          %-14s %s" $template [report_layer_details [dict get $layer $template]]]
+        }
+      }
+    }
+  }
+  if {[dict exists $specification instance]} {
+    utl::report "    Instance: [dict get $specification instance]"
+  }
+  if {[dict exists $specification macro]} {
+    utl::report "    Macro: [dict get $specification macro]"
+  }
+  if {[dict exists $specification orient]} {
+    utl::report "    Macro orientation: [dict get $specification orient]"
+  }
+  if {[dict exists $specification straps]} {
+    utl::report "    Straps"
+    dict for {layer_name layer} [dict get $specification straps] {
+      if {[dict exists $layer width]} {
+        utl::report "      Layer: $layer_name [report_layer_details $layer]"
+      } else {
+        utl::report "      Layer: $layer_name"
+        foreach template [dict keys $layer] {
+          utl::report [format "          %-14s %s" $template [report_layer_details [dict get $layer $template]]]
+        }
+      }
+    }
+  }
+  if {[dict exists $specification connect]} {
+    utl::report "    Connect: [dict get $specification connect]"
+  }
+}
+
+proc read_template_placement {} {
+  variable plan_template
+  variable def_units
+  variable prop_line
+
+  if {![is_defined_pdn_property "plan_template"]} {
+    define_template_grid
+  } else {
+    set plan_template {}
+    set prop_line [get_pdn_string_property_value "plan_template"]
+    while {![empty_propline]} {
+      set line [read_propline]
+      if {[llength $line] == 0} {continue}
+      set x  [expr round([lindex $line 0] * $def_units)]
+      set y  [expr round([lindex $line 1] * $def_units)]
+      set x1 [expr round([lindex $line 2] * $def_units)]
+      set y1 [expr round([lindex $line 3] * $def_units)]
+      set template [lindex $line end]
+
+      dict set plan_template $x $y $template
+    }
+  }
+}
+
+proc is_defined_pdn_property {name} {
+  variable block
+
+  if {[set pdn_props [::odb::dbBoolProperty_find $block PDN]] == "NULL"} {
+    return 0
+  }
+
+  if {[::odb::dbStringProperty_find $pdn_props $name] == "NULL"} {
+    return 0
+  }
+  return 1
+}
+
+proc get_pdn_string_property {name} {
+  variable block
+
+  if {[set pdn_props [::odb::dbBoolProperty_find $block PDN]] == "NULL"} {
+    set pdn_props [::odb::dbBoolProperty_create $block PDN 1]
+  }
+
+  if {[set prop [::odb::dbStringProperty_find $pdn_props $name]] == "NULL"} {
+    set prop [::odb::dbStringProperty_create $pdn_props $name ""]
+  }
+
+  return $prop
+}
+
+proc set_pdn_string_property_value {name value} {
+  set prop [get_pdn_string_property $name]
+  $prop setValue $value
+}
+
+proc get_pdn_string_property_value {name} {
+  set prop [get_pdn_string_property $name]
+
+  return [$prop getValue]
+}
+
+proc write_template_placement {} {
+  variable plan_template
+  variable template
+  variable def_units
+
+  set str ""
+  foreach x [lsort -integer [dict keys $plan_template]] {
+    foreach y [lsort -integer [dict keys [dict get $plan_template $x]]] {
+      set str [format "${str}%.3f %.3f %.3f %.3f %s ;\n" \
+        [expr 1.0 * $x / $def_units] [expr 1.0 * $y / $def_units] \
+        [expr 1.0 * ($x + [dict get $template width]) / $def_units] [expr 1.0 * ($y + [dict get $template height]) / $def_units] \
+        [dict get $plan_template $x $y]
+      ]
+    }
+  }
+
+  set_pdn_string_property_value "plan_template" $str
+}
+
+proc get_extent {polygon_set} {
+  set first_point  [lindex [odb::getPoints [lindex [odb::getPolygons $polygon_set] 0]] 0]
+  set minX [set maxX [$first_point getX]]
+  set minY [set maxY [$first_point getY]]
+
+  foreach shape [odb::getPolygons $polygon_set] {
+    foreach point [odb::getPoints $shape] {
+      set x [$point getX]
+      set y [$point getY]
+      set minX [expr min($minX,$x)]
+      set maxX [expr max($maxX,$x)]
+      set minY [expr min($minY,$y)]
+      set maxY [expr max($maxY,$y)]
+    }
+  }
+
+  return [list $minX $minY $maxX $maxY]
+}
+
+proc round_to_routing_grid {layer_name location} {
+  variable tech
+  variable block
+
+  set grid [$block findTrackGrid [$tech findLayer $layer_name]]
+
+  if {[get_dir $layer_name] == "hor"} {
+    set grid_points [$grid getGridY]
+  } else {
+    set grid_points [$grid getGridX]
+  }
+
+  set size [llength $grid_points]
+  set pos [expr ($size + 1) / 2]
+
+  if {[lsearch -exact $grid_points $location] != -1} {
+    return $location
+  }
+  set prev_pos -1
+  set size [expr ($size + 1) / 2]
+  while {!(([lindex $grid_points $pos] < $location) && ($location < [lindex $grid_points [expr $pos + 1]]))} {
+    if {$prev_pos == $pos} {utl::error "PDN" 51 "Infinite loop detected trying to round to grid."}
+    set prev_pos $pos
+    set size [expr ($size + 1) / 2]
+
+    if {$location > [lindex $grid_points $pos]} {
+      set pos [expr $pos + $size]
+    } else {
+      set pos [expr $pos - $size]
+    }
+    # debug "[lindex $grid_points $pos] < $location < [lindex $grid_points [expr $pos + 1]]"
+    # debug [expr (([lindex $grid_points $pos] < $location) && ($location < [lindex $grid_points [expr $pos + 1]]))]
+  }
+
+  return [lindex $grid_points $pos]
+}
+
+proc identify_channels {lower_layer_name upper_layer_name tag} {
+  variable block
+  variable stripe_locs
+
+  set channels {}
+  set wire_groups {}
+
+  set upper_pitch_check [expr round(1.1 * [get_grid_wire_pitch $upper_layer_name])]
+  set lower_pitch_check [expr round(1.1 * [get_grid_wire_pitch $lower_layer_name])]
+  # debug "stripes $lower_layer_name, tag: $tag, $stripe_locs($lower_layer_name,$tag)"
+  # debug "Direction (lower-$lower_layer_name): [get_dir $lower_layer_name] (upper-$upper_layer_name): [get_dir $upper_layer_name]"
+  # debug "Pitch check (lower): [ord::dbu_to_microns $lower_pitch_check], (upper): [ord::dbu_to_microns $upper_pitch_check]"
+  if {[get_dir $lower_layer_name] ==  "hor"} {
+    set channel_wires [odb::subtractSet $stripe_locs($lower_layer_name,$tag) [odb::bloatSet [odb::shrinkSet $stripe_locs($lower_layer_name,$tag) $upper_pitch_check 0] $upper_pitch_check 0]]
+    # Group wires with same xMin and xMax so that the channels form rectangles
+    foreach wire [odb::getRectangles $channel_wires] {
+      set xMin [$wire xMin]
+      set xMax [$wire xMax]
+      dict lappend wire_groups "$xMin,$xMax" [odb::newSetFromRect [$wire xMin] [$wire yMin] [$wire xMax] [$wire yMax]]
+    }
+    if {[dict size $wire_groups] > 1} {
+      dict for {position wires} $wire_groups {
+        lappend channels [odb::shrinkSet [odb::bloatSet [odb::orSets $wires] 0 $lower_pitch_check] 0 $lower_pitch_check]
+      }
+    }
+
+    set channels [odb::orSets $channels]
+    # debug "Channel wires: [llength [odb::getRectangles $channel_wires]]"
+    # debug "Channels: [llength [odb::getRectangles $channels]]"
+  } else {
+    set channel_wires [odb::subtractSet $stripe_locs($lower_layer_name,$tag) [odb::bloatSet [odb::shrinkSet $stripe_locs($lower_layer_name,$tag) 0 $upper_pitch_check] 0 $upper_pitch_check]]
+    # Group wires with same yMin and yMax so that the channels form rectangles
+    foreach wire [odb::getRectangles $channel_wires] {
+      set yMin [$wire yMin]
+      set yMax [$wire yMax]
+      dict lappend wire_groups "$yMin,$yMax" [odb::newSetFromRect [$wire xMin] [$wire yMin] [$wire xMax] [$wire yMax]]
+    }
+    if {[dict size $wire_groups] > 1} {
+      dict for {position wires} $wire_groups {
+        lappend channels [odb::shrinkSet [odb::bloatSet [odb::orSets $wires] $lower_pitch_check 0] $lower_pitch_check 0]
+      }
+    }
+
+    set channels [odb::orSets $channels]
+    # debug "Channel wires: [llength [odb::getRectangles $channel_wires]]"
+    # debug "Channels: [llength [odb::getRectangles $channels]]"
+  }
+
+  foreach rect [odb::getRectangles $channels] {
+    # debug "([ord::dbu_to_microns [$rect xMin]] [ord::dbu_to_microns [$rect yMin]]) - ([ord::dbu_to_microns [$rect xMax]] [ord::dbu_to_microns [$rect yMax]])"
+  }
+  # debug "Number of channels [llength [::odb::getPolygons $channels]]"
+
+  return $channels
+}
+
+
+proc repair_channel {channel layer_name tag min_size} {
+  variable stripe_locs
+
+  if {[get_dir $layer_name] == "hor"} {
+    set channel_height [$channel dx]
+  } else {
+    set channel_height [$channel dy]
+  }
+  set width [get_grid_wire_width $layer_name]
+
+  set xMin [$channel xMin]
+  set xMax [$channel xMax]
+  set yMin [$channel yMin]
+  set yMax [$channel yMax]
+
+  if {$tag == "POWER"} {
+    set other_tag "GROUND"
+  } else {
+    set other_tag "POWER"
+  }
+
+  if {[channel_has_pg_strap $channel $layer_name $other_tag]} {
+    set other_strap [lindex [odb::getRectangles [odb::andSet [odb::newSetFromRect $xMin $yMin $xMax $yMax] $stripe_locs($layer_name,$other_tag)]] 0]
+
+    if {[get_dir $layer_name] == "hor"} {
+      set center [expr ($xMax + $xMin) / 2]
+      set mid_channel [expr ($yMax + $yMin) / 2]
+      if {$xMax - $xMin < $min_size} {
+        set xMin [expr $center - $min_size / 2]
+        set xMax [expr $center + $min_size / 2]
+      }
+      set channel_spacing [get_grid_channel_spacing $layer_name [expr $xMax - $xMin]]
+
+      set other_strap_mid [expr ([$other_strap yMin] + [$other_strap yMax]) / 2]
+      if {($mid_channel <= $other_strap_mid) && ([$other_strap yMin] - $channel_spacing - $width > $yMin)} {
+        # debug "Stripe below $other_strap"
+        set stripe [odb::newSetFromRect $xMin [expr [$other_strap yMin] - $channel_spacing - $width] $xMax [expr [$other_strap yMin] - $channel_spacing]]
+      } elseif {($mid_channel > $other_strap_mid) && ([$other_strap yMax] + $channel_spacing + $width < $yMax)} {
+        # debug "Stripe above $other_strap"
+        set stripe [odb::newSetFromRect $xMin [expr [$other_strap yMax] + $channel_spacing] $xMax [expr [$other_strap yMax] + $channel_spacing + $width]]
+      } else {
+	set stripe {}
+        utl::warn PDN 169 "Cannot fit additional $tag horizontal strap in channel ([ord::dbu_to_microns $xMin] [ord::dbu_to_microns $yMin]) - ([ord::dbu_to_microns $xMax], [ord::dbu_to_microns $yMax])"
+      }
+    } else {
+      set center [expr ($yMax + $yMin) / 2]
+      set mid_channel [expr ($xMax + $xMin) / 2]
+      if {$yMax - $yMin < $min_size} {
+        set yMin [expr $center - $min_size / 2]
+        set yMax [expr $center + $min_size / 2]
+      }
+      set channel_spacing [get_grid_channel_spacing $layer_name [expr $yMax - $yMin]]
+
+      set other_strap_mid [expr ([$other_strap xMin] + [$other_strap xMax]) / 2]
+      if {($mid_channel <= $other_strap_mid) && ([$other_strap xMin] - $channel_spacing - $width > $xMin)} {
+        # debug "Stripe left of $other_strap on layer $layer_name, spacing: $channel_spacing, width $width, strap_edge: [$other_strap xMin]"
+        set stripe [odb::newSetFromRect [expr [$other_strap xMin] - $channel_spacing - $width] $yMin [expr [$other_strap xMin] - $channel_spacing] $yMax]
+      } elseif {($mid_channel > $other_strap_mid) && ([$other_strap xMax] + $channel_spacing + $width < $xMax)} {
+        # debug "Stripe right of $other_strap"
+        set stripe [odb::newSetFromRect [expr [$other_strap xMax] + $channel_spacing] $yMin [expr [$other_strap xMax] + $channel_spacing + $width] $yMax]
+      } else {
+	set stripe {}
+        utl::warn PDN 170 "Cannot fit additional $tag vertical strap in channel ([ord::dbu_to_microns $xMin] [ord::dbu_to_microns $yMin]) - ([ord::dbu_to_microns $xMax], [ord::dbu_to_microns $yMax])"
+      }
+    }
+  } else {
+    if {[get_dir $layer_name] == "hor"} {
+      set channel_spacing [get_grid_channel_spacing $layer_name [expr $xMax - $xMin]]
+      set routing_grid [round_to_routing_grid $layer_name [expr ($yMax + $yMin - $channel_spacing) / 2]]
+      if {([expr $routing_grid - $width / 2] < $yMin) || ([expr $routing_grid + $width / 2] > $yMax)} {
+        utl::warn "PDN" 171 "Channel ([ord::dbu_to_microns $xMin] [ord::dbu_to_microns $yMin] [ord::dbu_to_microns $xMax] [ord::dbu_to_microns $yMax]) too narrow. Channel on layer $layer_name must be at least [ord::dbu_to_microns [expr round(2.0 * $width + $channel_spacing)]] wide."
+      }
+
+      set stripe [odb::newSetFromRect $xMin [expr $routing_grid - $width / 2] $xMax [expr $routing_grid + $width / 2]]
+    } else {
+      set channel_spacing [get_grid_channel_spacing $layer_name [expr $yMax - $yMin]]
+      set routing_grid [round_to_routing_grid $layer_name [expr ($xMax + $xMin - $channel_spacing) / 2]]
+
+      if {([expr $routing_grid - $width / 2] < $xMin) || ([expr $routing_grid + $width / 2] > $xMax)} {
+        utl::warn "PDN" 172 "Channel ([ord::dbu_to_microns $xMin] [ord::dbu_to_microns $yMin] [ord::dbu_to_microns $xMax] [ord::dbu_to_microns $yMax]) too narrow. Channel on layer $layer_name must be at least [ord::dbu_to_microns [expr round(2.0 * $width + $channel_spacing)]] wide."
+      }
+
+      set stripe [odb::newSetFromRect [expr $routing_grid - $width / 2] $yMin [expr $routing_grid + $width / 2] $yMax]
+    }
+  }
+
+  if {$stripe != {}} {
+    add_stripe $layer_name $tag $stripe
+  }
+}
+
+proc channel_has_pg_strap {channel layer_name tag}  {
+  variable stripe_locs
+  # debug "start, channel: $channel, layer: $layer_name"
+  # debug "       ([ord::dbu_to_microns [$channel xMin]] [ord::dbu_to_microns [$channel yMin]]) - ([ord::dbu_to_microns [$channel xMax]] [ord::dbu_to_microns [$channel yMax]])"
+
+  set power_strap 0
+  set ground_strap 0
+
+  set channel_set [odb::newSetFromRect [$channel xMin] [$channel yMin] [$channel xMax] [$channel yMax]]
+  set check_set [odb::andSet $stripe_locs($layer_name,$tag) $channel_set]
+
+  foreach rect [odb::getRectangles $check_set] {
+    if {[get_dir $layer_name] == "ver" && [$rect dx] < [get_grid_wire_width $layer_name]} {continue}
+    if {[get_dir $layer_name] == "hor" && [$rect dy] < [get_grid_wire_width $layer_name]} {continue}
+    # debug "Overlap found"
+    # debug "       Direction: [get_dir $layer_name]"
+    # debug "       Layer width: [get_grid_wire_width $layer_name]"
+    # debug "       ([ord::dbu_to_microns [$rect xMin]] [ord::dbu_to_microns [$rect yMin]]) - ([ord::dbu_to_microns [$rect xMax]] [ord::dbu_to_microns [$rect yMax]])"
+    return 1
+  }
+
+  # debug "end: channel needs repair"
+  return 0
+}
+
+proc process_channels {} {
+  set layers [get_grid_channel_layers]
+  set lower_layers [lrange $layers 0 end-1]
+  set upper_layers [lrange $layers 1 end]
+  foreach lower_layer_name $lower_layers upper_layer_name $upper_layers {
+    foreach tag {POWER GROUND} {
+      set channels [identify_channels $lower_layer_name $upper_layer_name $tag]
+      if {$channels == {}} {continue}
+      # debug "Tag: $tag, Channels found: [llength [odb::getPolygons $channels]]"
+      foreach channel [::odb::getRectangles $channels] {
+        if {![channel_has_pg_strap $channel $upper_layer_name $tag]} {
+          set next_upper_layer_idx [expr [lsearch -exact $layers $upper_layer_name] + 1]
+          if {$next_upper_layer_idx < [llength $layers]} {
+            set next_upper_layer [lindex $layers $next_upper_layer_idx]
+            set min_size [expr [get_grid_wire_pitch $next_upper_layer] + [get_grid_wire_width $next_upper_layer]]
+          } else {
+            set min_size 0
+          }
+          # debug "Repair channel: ([ord::dbu_to_microns [$channel xMin]] [ord::dbu_to_microns [$channel yMin]])-([ord::dbu_to_microns [$channel xMax]] [ord::dbu_to_microns [$channel yMax]]]), tag: $tag, layer: $upper_layer_name, min_size: $min_size"
+          repair_channel $channel $upper_layer_name $tag $min_size
+        }
+      }
+      merge_stripes
+    }
+  }
+}
+
+proc get_stdcell_plus_area {} {
+  variable stdcell_area
+  variable stdcell_plus_area
+
+  if {$stdcell_area == ""} {
+    get_stdcell_area
+  }
+  # debug "stdcell_area      [get_extent $stdcell_area]"
+  # debug "stdcell_plus_area [get_extent $stdcell_plus_area]"
+  return $stdcell_plus_area
+}
+
+proc get_stdcell_area {} {
+  variable stdcell_area
+  variable stdcell_plus_area
+
+  if {$stdcell_area != ""} {return $stdcell_area}
+  set rails_width [get_rails_max_width]
+
+  set rows [[ord::get_db_block] getRows]
+  set first_row [[lindex $rows 0] getBBox]
+
+  set minX [$first_row xMin]
+  set maxX [$first_row xMax]
+  set minY [$first_row yMin]
+  set maxY [$first_row yMax]
+  set stdcell_area [odb::newSetFromRect $minX $minY $maxX $maxY]
+  set stdcell_plus_area [odb::newSetFromRect $minX [expr $minY - $rails_width / 2] $maxX [expr $maxY + $rails_width / 2]]
+
+  foreach row [lrange $rows 1 end] {
+    set box [$row getBBox]
+    set minX [$box xMin]
+    set maxX [$box xMax]
+    set minY [$box yMin]
+    set maxY [$box yMax]
+    set stdcell_area [odb::orSet $stdcell_area [odb::newSetFromRect $minX $minY $maxX $maxY]]
+    set stdcell_plus_area [odb::orSet $stdcell_plus_area [odb::newSetFromRect $minX [expr $minY - $rails_width / 2] $maxX [expr $maxY + $rails_width / 2]]]
+  }
+
+  return $stdcell_area
+}
+
+proc find_core_area {} {
+  variable block
+
+  set area [get_stdcell_area]
+
+  return [get_extent $area]
+}
+
+proc get_rails_max_width {} {
+  variable design_data
+  variable default_grid_data
+
+  set max_width 0
+  foreach layer [get_rails_layers] {
+     if {[dict exists $default_grid_data rails $layer]} {
+       if {[set width [dict get $default_grid_data rails $layer width]] > $max_width} {
+         set max_width $width
+       }
+     }
+  }
+  if {![dict exists $default_grid_data units]} {
+    set max_width [ord::microns_to_dbu $max_width]
+  }
+  return $max_width
+}
+
+
+# This is a proc to get the first voltage domain that overlaps with the input box
+proc get_voltage_domain {llx lly urx ury} {
+  variable block
+  variable design_data
+  variable voltage_domains
+
+
+  set name [dict get $design_data core_domain]
+  foreach domain_name [dict keys $voltage_domains] {
+    if {$domain_name == [dict get $design_data core_domain]} {continue}
+    set domain [$block findRegion $domain_name]
+    set rect [lindex [$domain getBoundaries] 0]
+
+    set domain_xMin [$rect xMin]
+    set domain_yMin [$rect yMin]
+    set domain_xMax [$rect xMax]
+    set domain_yMax [$rect yMax]
+
+    if {!($domain_yMin >= $ury || $domain_xMin >= $urx || $domain_xMax <= $llx || $domain_yMax <= $lly)} {
+      set name [$domain getName]
+      break
+    }
+  }
+  return $name
+}
+
+proc get_voltage_domain_power {domain} {
+  variable voltage_domains
+
+  return [dict get $voltage_domains $domain primary_power]
+}
+
+proc get_voltage_domain_secondary_power {domain} {
+  variable voltage_domains
+  
+  if {[dict exists $voltage_domains $domain secondary_power]} {
+    return [dict get $voltage_domains $domain secondary_power]
+  } else {
+    return []
+  }
+}
+
+proc get_voltage_domain_ground {domain} {
+  variable voltage_domains
+
+  return [dict get $voltage_domains $domain primary_ground]
+}
+
+# This proc is to split core domain's power stripes if they cross interal voltage domains that have different pwr/gnd nets
+proc update_mesh_stripes_with_volatge_domains {tag lay snet_name} {
+  variable block
+  variable stripes
+  variable grid_data
+  variable design_data
+  variable voltage_domains
+
+  set rails_width [get_rails_max_width]
+
+  set stdcell_area [get_extent [get_stdcell_area]]
+  set stdcell_min_x [lindex $stdcell_area 0]
+  set stdcell_min_y [lindex $stdcell_area 1]
+  set stdcell_max_x [lindex $stdcell_area 2]
+  set stdcell_max_y [lindex $stdcell_area 3]
+
+  set ring_adjustment 0
+  if {[set ring_vertical_layer [get_core_ring_vertical_layer_name]] != ""} {
+    if {[dict exists $grid_data core_ring $ring_vertical_layer pad_offset]} {
+      set pad_area [find_pad_offset_area]
+      set offset [expr [dict get $grid_data core_ring $ring_vertical_layer pad_offset]]
+      set ring_adjustment [expr $stdcell_min_x - ([lindex $pad_area 0] + $offset)]
+    }
+    if {[dict exists $grid_data core_ring $ring_vertical_layer core_offset]} {
+      set ring_adjustment [expr \
+        [dict get $grid_data core_ring $ring_vertical_layer core_offset] + \
+        [dict get $grid_data core_ring $ring_vertical_layer spacing] + \
+        3 * [dict get $grid_data core_ring $ring_vertical_layer width] / 2 \
+      ]
+    }
+  }
+
+  set first_row [lindex [$block getRows] 0]
+  set row_site [$first_row getSite]
+  set site_width [$row_site getWidth]
+  set row_height [$row_site getHeight]
+
+  # This voltage domain to core domain margin is hard coded for now
+  set MARGIN 6
+  set X_MARGIN [expr ($MARGIN * $row_height / $site_width) * $site_width]
+  set Y_MARGIN [expr $MARGIN * $row_height]
+
+  foreach domain_name [dict keys $voltage_domains] {
+    if {$domain_name == [dict get $design_data core_domain]} {continue}
+    set domain [$block findRegion $domain_name]
+    set first_rect [lindex [$domain getBoundaries] 0]
+
+    # voltage domain area
+    set domain_xMin [expr [$first_rect xMin]]
+    set domain_yMin [expr [$first_rect yMin]]
+    set domain_xMax [expr [$first_rect xMax]]
+    set domain_yMax [expr [$first_rect yMax]]
+
+    # voltage domain area + margin
+    set domain_boundary_xMin [expr [$first_rect xMin] - $X_MARGIN]
+    set domain_boundary_yMin [expr [$first_rect yMin] - $Y_MARGIN + $rails_width / 2]
+    set domain_boundary_xMax [expr [$first_rect xMax] + $X_MARGIN]
+    set domain_boundary_yMax [expr [$first_rect yMax] + $Y_MARGIN - $rails_width / 2]
+
+    if {[get_dir $lay] == "hor"} {
+      if {$domain_boundary_xMin < $stdcell_min_x + $site_width} {
+        set domain_boundary_xMin [expr $stdcell_min_x - $ring_adjustment]
+      }
+      if {$domain_boundary_xMax > $stdcell_max_x - $site_width} {
+        set domain_boundary_xMax [expr $stdcell_max_x + $ring_adjustment]
+      }
+    } else {
+      if {$domain_boundary_yMin < $stdcell_min_y + $row_height} {
+        set domain_boundary_yMin [expr $stdcell_min_y - $ring_adjustment]
+      }
+      if {$domain_boundary_yMax > $stdcell_max_y - $row_height} {
+        set domain_boundary_yMax [expr $stdcell_max_y + $ring_adjustment]
+      }
+    }
+
+    # Core domain's pwr/gnd nets that are not shared should not cross the entire voltage domain area
+    set boundary_box \
+        [odb::newSetFromRect \
+          $domain_boundary_xMin \
+          $domain_boundary_yMin \
+          $domain_boundary_xMax \
+          $domain_boundary_yMax \
+        ]
+
+    if {[get_dir $lay] == "hor"} {
+      set domain_box \
+        [odb::newSetFromRect \
+          $domain_boundary_xMin \
+          $domain_yMin \
+          $domain_boundary_xMax \
+          $domain_yMax \
+        ]
+    } else {
+      set domain_box \
+        [odb::newSetFromRect \
+          $domain_xMin \
+          $domain_boundary_yMin \
+          $domain_xMax \
+          $domain_boundary_yMax \
+        ]
+    }
+    # Core domain's pwr/gnd nets shared with a voltage domain should not cross the domains' pwr/gnd rings
+    set boundary_box_for_crossing_core_net [odb::subtractSet $boundary_box $domain_box]
+
+    for {set i 0} {$i < [llength $stripes($lay,$tag)]} {incr i} {
+      set updated_polygonSet [lindex $stripes($lay,$tag) $i]
+      # Check if core domain's power is the same as voltage domain's power 
+      if {$snet_name == [get_voltage_domain_power $domain_name] ||
+          $snet_name == [get_voltage_domain_ground $domain_name]} {
+        set updated_polygonSet [odb::subtractSet $updated_polygonSet $boundary_box_for_crossing_core_net]
+      } else {
+        set updated_polygonSet [odb::subtractSet $updated_polygonSet $boundary_box]
+      }
+      # This if statemet prevents from deleting domain rings
+      if {[llength [odb::getPolygons $updated_polygonSet]] > 0} {
+        set stripes($lay,$tag) [lreplace $stripes($lay,$tag) $i $i $updated_polygonSet]
+      }
+    }
+  }
+}
+
+# This proc is to check if a pwr/gnd net is unique for all voltage domains, the setWildConnected can be used
+proc check_snet_is_unique {net} {
+  variable voltage_domains
+
+  set is_unique_power 1
+  foreach vd_key [dict keys $voltage_domains] {
+    if {[dict get $voltage_domains $vd_key primary_power] != [$net getName]} {
+      set is_unique_power 0
+      break
+    }
+  }
+
+  set is_unique_ground 1
+  foreach vd_key [dict keys $voltage_domains] {
+    if {[dict get $voltage_domains $vd_key primary_ground] != [$net getName]} {
+      set is_unique_ground 0
+      break
+    }
+  }
+
+  return [expr $is_unique_power || $is_unique_ground]
+
+}
+
+# This proc generates power rings for voltage domains, tags for the core domain are POWER/GROUND, tags for the other
+# voltage domains are defined as POWER_<pwr-net> and GROUND_<gnd-net>
+proc generate_voltage_domain_rings {core_ring_data} {
+  variable block
+  variable voltage_domains
+  variable grid_data
+  variable design_data
+
+  foreach domain_name [dict keys $voltage_domains] {
+    if {$domain_name == [dict get $design_data core_domain]} {continue}
+    set domain [$block findRegion $domain_name]
+    set rect [lindex [$domain getBoundaries] 0]
+    set power_net [get_voltage_domain_power $domain_name]
+    set secondary_power_net [get_voltage_domain_secondary_power $domain_name]
+    set ground_net [get_voltage_domain_ground $domain_name]
+    
+    set domain_xMin [$rect xMin]
+    set domain_yMin [$rect yMin]
+    set domain_xMax [$rect xMax]
+    set domain_yMax [$rect yMax]
+    dict for {layer layer_info} $core_ring_data {
+      if {[dict exists $layer_info core_offset]} {
+        set offset [dict get $layer_info core_offset]
+
+        set spacing [dict get $layer_info spacing]
+        set width [dict get $layer_info width]
+
+        set inner_lx [expr $domain_xMin - $offset]
+        set inner_ly [expr $domain_yMin - $offset]
+        set inner_ux [expr $domain_xMax + $offset]
+        set inner_uy [expr $domain_yMax + $offset]
+
+        set outer_lx [expr $domain_xMin - $offset - $spacing - $width]
+        set outer_ly [expr $domain_yMin - $offset - $spacing - $width]
+        set outer_ux [expr $domain_xMax + $offset + $spacing + $width]
+        set outer_uy [expr $domain_yMax + $offset + $spacing + $width]
+      }
+      set number_of_rings 0
+      if {[get_dir $layer] == "hor"} {
+        set lower_inner_ring \
+          [odb::newSetFromRect \
+            [expr $inner_lx - $width / 2] \
+            [expr $inner_ly - $width / 2] \
+            [expr $inner_ux + $width / 2] \
+            [expr $inner_ly + $width / 2] \
+          ]
+        set upper_inner_ring \
+          [odb::newSetFromRect \
+            [expr $inner_lx - $width / 2] \
+            [expr $inner_uy - $width / 2] \
+            [expr $inner_ux + $width / 2] \
+            [expr $inner_uy + $width / 2] \
+          ]
+        set lower_outer_ring \
+          [odb::newSetFromRect \
+            [expr $outer_lx - $width / 2] \
+            [expr $outer_ly - $width / 2] \
+            [expr $outer_ux + $width / 2] \
+            [expr $outer_ly + $width / 2] \
+          ]
+        set upper_outer_ring \
+          [odb::newSetFromRect \
+            [expr $outer_lx - $width / 2] \
+            [expr $outer_uy - $width / 2] \
+            [expr $outer_ux + $width / 2] \
+            [expr $outer_uy + $width / 2] \
+          ]
+        
+        if {$power_net == [get_voltage_domain_power [dict get $design_data core_domain]]} {
+          add_stripe $layer "POWER" $lower_inner_ring
+          add_stripe $layer "POWER" $upper_inner_ring
+        } else {
+          add_stripe $layer "POWER_$power_net" $lower_inner_ring
+          add_stripe $layer "POWER_$power_net" $upper_inner_ring
+        }
+        if {$ground_net == [get_voltage_domain_ground [dict get $design_data core_domain]]} {
+          add_stripe $layer "GROUND" $lower_outer_ring
+          add_stripe $layer "GROUND" $upper_outer_ring
+        } else {
+          add_stripe $layer "GROUND_$ground_net" $lower_outer_ring
+          add_stripe $layer "GROUND_$ground_net" $upper_outer_ring
+        }
+        
+      	set number_of_rings [llength $secondary_power_net]
+        foreach secondary_power $secondary_power_net {
+          set outer_lx [expr $outer_lx - $spacing - $width]
+          set outer_ly [expr $outer_ly - $spacing - $width]
+          set outer_ux [expr $outer_ux + $spacing + $width] 
+    	    set outer_uy [expr $outer_uy + $spacing + $width]
+
+          set upper_domain_power \
+            [odb::newSetFromRect \
+              [expr $outer_lx - $width / 2] \
+              [expr $outer_uy - $width / 2] \
+              [expr $outer_ux + $width / 2] \
+              [expr $outer_uy + $width / 2] \
+            ]
+
+          set lower_domain_power \
+            [odb::newSetFromRect \
+              [expr $outer_lx - $width / 2] \
+              [expr $outer_ly - $width / 2] \
+              [expr $outer_ux + $width / 2] \
+              [expr $outer_ly + $width / 2] \
+            ]
+
+          add_stripe $layer "POWER_$secondary_power" $upper_domain_power
+          add_stripe $layer "POWER_$secondary_power" $lower_domain_power
+        }
+      } else {
+        set lhs_inner_ring \
+          [odb::newSetFromRect \
+            [expr $inner_lx - $width / 2] \
+            [expr $inner_ly - $width / 2] \
+            [expr $inner_lx + $width / 2] \
+            [expr $inner_uy + $width / 2] \
+          ]
+        set rhs_inner_ring \
+          [odb::newSetFromRect \
+            [expr $inner_ux - $width / 2] \
+            [expr $inner_ly - $width / 2] \
+            [expr $inner_ux + $width / 2] \
+            [expr $inner_uy + $width / 2] \
+          ]
+        set lhs_outer_ring \
+          [odb::newSetFromRect \
+            [expr $outer_lx - $width / 2] \
+            [expr $outer_ly - $width / 2] \
+            [expr $outer_lx + $width / 2] \
+            [expr $outer_uy + $width / 2] \
+          ]
+        set rhs_outer_ring \
+          [odb::newSetFromRect \
+            [expr $outer_ux - $width / 2] \
+            [expr $outer_ly - $width / 2] \
+            [expr $outer_ux + $width / 2] \
+            [expr $outer_uy + $width / 2] \
+          ]
+
+        if {$power_net == [get_voltage_domain_power [dict get $design_data core_domain]]} {
+          add_stripe $layer "POWER" $lhs_inner_ring
+          add_stripe $layer "POWER" $rhs_inner_ring
+        } else {
+          add_stripe $layer "POWER_$power_net" $lhs_inner_ring
+          add_stripe $layer "POWER_$power_net" $rhs_inner_ring
+        }
+        if {$ground_net == [get_voltage_domain_ground [dict get $design_data core_domain]]} {
+          add_stripe $layer "GROUND" $lhs_outer_ring
+          add_stripe $layer "GROUND" $rhs_outer_ring
+        } else {
+          add_stripe $layer "GROUND_$ground_net" $lhs_outer_ring
+          add_stripe $layer "GROUND_$ground_net" $rhs_outer_ring
+        }
+
+        foreach secondary_power $secondary_power_net {
+          set outer_lx [expr $outer_lx - $spacing - $width]
+          set outer_ly [expr $outer_ly - $spacing - $width]
+          set outer_ux [expr $outer_ux + $spacing + $width]
+          set outer_uy [expr $outer_uy + $spacing + $width]
+  
+          set lhs_domain_power \
+            [odb::newSetFromRect \
+              [expr $outer_lx - $width / 2] \
+              [expr $outer_ly - $width / 2] \
+              [expr $outer_lx + $width / 2] \
+              [expr $outer_uy + $width / 2] \
+            ]
+
+          set rhs_domain_power \
+            [odb::newSetFromRect \
+              [expr $outer_ux - $width / 2] \
+              [expr $outer_ly - $width / 2] \
+              [expr $outer_ux + $width / 2] \
+              [expr $outer_uy + $width / 2] \
+            ]
+
+          add_stripe $layer "POWER_$secondary_power" $lhs_domain_power
+          add_stripe $layer "POWER_$secondary_power" $rhs_domain_power
+        }
+      }
+    }
+  }
+}
+
+
+# This proc detects pins used in pdn.cfg for global connections
+proc get_valid_mterms {net_name} {
+  variable global_connections
+  variable default_global_connections
+ 
+  if {$global_connections == {}} {
+    set global_connections $default_global_connections
+  }
+
+  set mterms_list {}
+  if {![dict exists $global_connections $net_name]} {
+    utl::error PDN  174 "Net $net_name has no global connections defined."
+  }
+
+  foreach pattern [dict get $global_connections $net_name] {
+    lappend mterms_list [dict get $pattern pin_name]
+  }
+  return $mterms_list
+}
+
+proc core_area_boundary {} {
+  variable design_data
+  variable template
+  variable metal_layers
+  variable grid_data
+
+  set core_area [find_core_area]
+  # We need to allow the rails to extend by half a rails width in the y direction, since the rails overlap the core_area
+
+  set llx [lindex $core_area 0]
+  set lly [lindex $core_area 1]
+  set urx [lindex $core_area 2]
+  set ury [lindex $core_area 3]
+
+  if {[dict exists $template width]} {
+    set width [dict get $template width]
+  } else {
+    set width 2000
+  }
+  if {[dict exists $template height]} {
+    set height [dict get $template height]
+  } else {
+    set height 2000
+  }
+
+  # Add blockages around the outside of the core area in order to trim back the templates.
+  #
+  set boundary [odb::newSetFromRect [expr $llx - $width] [expr $lly - $height] $llx [expr $ury + $height]]
+  set boundary [odb::orSet $boundary [odb::newSetFromRect [expr $llx - $width] [expr $lly - $height] [expr $urx + $width] $lly]]
+  set boundary [odb::orSet $boundary [odb::newSetFromRect [expr $llx - $width] $ury [expr $urx + $width] [expr $ury + $height]]]
+  set boundary [odb::orSet $boundary [odb::newSetFromRect $urx [expr $lly - $height] [expr $urx + $width] [expr $ury + $height]]]
+  set boundary [odb::subtractSet $boundary [get_stdcell_plus_area]]
+
+  foreach layer $metal_layers {
+    if {[dict exists $grid_data core_ring] && [dict exists $grid_data core_ring $layer]} {continue}
+    dict set blockages $layer $boundary
+  }
+
+  return $blockages
+}
+
+proc get_instance_blockages {insts} {
+  variable instances
+  set blockages {}
+
+  foreach inst $insts {
+    foreach layer [get_macro_blockage_layers $inst] {
+      # debug "Inst $inst"
+      # debug "Macro boundary: [dict get $instances $inst macro_boundary]"
+      # debug "Halo boundary:  [dict get $instances $inst halo_boundary]"
+      set box [odb::newSetFromRect [get_instance_llx $inst] [get_instance_lly $inst] [get_instance_urx $inst] [get_instance_ury $inst]]
+      if {[dict exists $blockages $layer]} {
+        dict set blockages $layer [odb::orSet [dict get $blockages $layer] $box]
+      } else {
+        dict set blockages $layer $box
+      }
+    }
+  }
+
+  return $blockages
+}
+
+proc define_template_grid {} {
+  variable design_data
+  variable template
+  variable plan_template
+  variable block
+  variable default_grid_data
+  variable default_template_name
+
+  set core_area [dict get $design_data config core_area]
+  set llx [lindex $core_area 0]
+  set lly [lindex $core_area 1]
+  set urx [lindex $core_area 2]
+  set ury [lindex $core_area 3]
+
+  set core_width  [expr $urx - $llx]
+  set core_height [expr $ury - $lly]
+
+  set template_width  [dict get $template width]
+  set template_height [dict get $template height]
+  set x_sections [expr round($core_width  / $template_width)]
+  set y_sections [expr round($core_height / $template_height)]
+
+  dict set template offset x [expr [lindex $core_area 0] + ($core_width - $x_sections * $template_width) / 2]
+  dict set template offset y [expr [lindex $core_area 1] + ($core_height - $y_sections * $template_height) / 2]
+
+  if {$default_template_name == {}} {
+    set template_name [lindex [dict get $default_grid_data template names] 0]
+  } else {
+    set template_name $default_template_name
+  }
+
+  for {set i -1} {$i <= $x_sections} {incr i} {
+    for {set j -1} {$j <= $y_sections} {incr j} {
+      set llx [expr $i * $template_width + [dict get $template offset x]]
+      set lly [expr $j * $template_height + [dict get $template offset y]]
+
+      dict set plan_template $llx $lly $template_name
+    }
+  }
+  write_template_placement
+}
+
+proc set_blockages {these_blockages} {
+  variable blockages
+
+  set blockages $these_blockages
+}
+
+proc get_blockages {} {
+  variable blockages
+
+  return $blockages
+}
+
+proc add_blockage {layer blockage} {
+  variable blockages
+
+  if {[dict exists $blockages $layer]} {
+    dict set blockages $layer [odb::orSet [dict get $blockages $layer] $blockage]
+  } else {
+    dict set blockages $layer $blockage
+  }
+}
+
+proc add_padcell_blockage {layer blockage} {
+  variable padcell_blockages
+
+  if {[dict exists $padcell_blockages $layer]} {
+    dict set padcell_blockages $layer [odb::orSet [dict get $padcell_blockages $layer] $blockage]
+  } else {
+    dict set padcell_blockages $layer $blockage
+  }
+}
+
+proc apply_padcell_blockages {} {
+  variable padcell_blockages
+  variable global_connections
+
+  dict for {layer_name blockages} $padcell_blockages {
+    add_blockage $layer_name $blockages
+  }
+}
+
+proc add_blockages {more_blockages} {
+  variable blockages
+
+  dict for {layer blockage} $more_blockages {
+    add_blockage $layer $blockage
+  }
+}
+
+proc add_macro_based_grids {} {
+  variable grid_data
+  variable design_data
+  variable verbose
+
+  if {![dict exists $design_data grid macro]} {return}
+
+  foreach {key grid_data} [dict get $design_data grid macro] {
+    if {![dict exists $grid_data _related_instances]} {continue}
+    set instances [dict get $grid_data _related_instances]
+
+    set_blockages {}
+    if {[llength [dict keys $instances]] > 0} {
+      utl::info "PDN" 10 "Inserting macro grid for [llength [dict keys $instances]] macros."
+      foreach instance [dict keys $instances] {
+        if {$verbose == 1} {
+          utl::info "PDN" 34 "  - grid [dict get $grid_data name] for instance $instance"
+        }
+
+        if {[dict exists $grid_data grid_over_pg_pins]} {
+          # debug "Grid over pins: [get_instance_pg_pins_area $instance]"
+          dict set grid_data area [get_instance_pg_pins_area $instance]
+        } else {
+          # debug "Grid boundary: [get_instance_pg_pins_area $instance]"
+          dict set grid_data area [dict get $instances $instance macro_boundary]
+        }
+        add_grid
+
+        # debug "Generate vias for [dict get $design_data power_nets] [dict get $design_data ground_nets]"
+        foreach pwr_net [dict get $design_data power_nets] {
+          generate_grid_vias "POWER" $pwr_net
+        }
+        foreach gnd_net [dict get $design_data ground_nets] {
+          generate_grid_vias "GROUND" $gnd_net
+        }
+      }
+    }
+  }
+}
+
+proc plan_grid {} {
+  variable design_data
+  variable instances
+  variable default_grid_data
+  variable def_units
+  variable grid_data
+
+  ################################## Main Code #################################
+
+  if {![dict exists $design_data grid macro]} {
+    utl::warn "PDN" 18 "No macro grid specifications found - no straps added."
+  }
+
+  utl::info "PDN" 11 "****** INFO ******"
+
+  print_strategy stdcell [get_stdcell_specification]
+
+  if {[dict exists $design_data grid macro]} {
+    dict for {name specification} [dict get $design_data grid macro] {
+      print_strategy macro $specification
+    }
+  }
+
+  utl::info "PDN" 12 "**** END INFO ****"
+
+  set specification $default_grid_data
+  if {[dict exists $specification name]} {
+    utl::info "PDN" 13 "Inserting stdcell grid - [dict get $specification name]."
+  } else {
+    utl::info "PDN" 14 "Inserting stdcell grid."
+  }
+
+  if {![dict exists $specification area]} {
+    dict set specification area [dict get $design_data config core_area]
+  }
+
+  set grid_data $specification
+
+  set boundary [odb::newSetFromRect {*}[dict get $grid_data area]]
+  set insts_in_core_area [filtered_insts_within $instances $boundary]
+
+  set_blockages [get_instance_blockages [dict keys $insts_in_core_area]]
+  add_blockages [core_area_boundary]
+  if {[dict exists $specification template]} {
+    read_template_placement
+  }
+
+  add_grid
+  #Dinesh-A: Core Ring without Strap
+  if {[info exists $grid_data]} {
+  	process_channels
+  }
+
+  foreach pwr_net [dict get $design_data power_nets] {
+    generate_grid_vias "POWER" $pwr_net
+  }
+
+  foreach gnd_net [dict get $design_data ground_nets] {
+    generate_grid_vias "GROUND" $gnd_net
+  }
+
+  add_macro_based_grids
+ 
+}
+
+proc opendb_update_grid {} {
+  utl::info "PDN" 15 "Writing to database."
+  export_opendb_vias
+  export_opendb_specialnets
+  export_opendb_power_pins
+}
+  
+proc set_verbose {} {
+  variable verbose
+
+  set verbose 1
+}
+
+proc apply {args} {
+  variable verbose
+  variable default_grid_data
+
+  if {[llength $args] > 0 && $verbose} {
+    set config [lindex $args 0]
+    utl::info "PDN" 16 "Power Delivery Network Generator: Generating PDN\n  config: $config"
+  }
+
+  if {[llength $args] == 1} {
+    set PDN_cfg [lindex $args 0]
+    if {![file exists $PDN_cfg]} {
+      utl::error "PDN" 62 "File $PDN_cfg does not exist."
+    }
+ 
+    if {![file_exists_non_empty $PDN_cfg]} {
+      utl::error "PDN" 28 "File $PDN_cfg is empty."
+    }
+    source $PDN_cfg
+  }
+
+  init {*}$args
+  complete_macro_grid_specifications
+  set default_grid_data [get_stdcell_specification]
+
+  plan_grid
+
+  write_pdn_strategy
+
+  opendb_update_grid
+}
+}
diff --git a/hacks/src/OpenROAD/Resizer.cc b/hacks/src/OpenROAD/Resizer.cc
index a339071..300c6c4 100644
--- a/hacks/src/OpenROAD/Resizer.cc
+++ b/hacks/src/OpenROAD/Resizer.cc
@@ -37,7 +37,6 @@
 
 #include "rsz/SteinerTree.hh"
 
-#include "ord/OpenRoad.hh"
 #include "gui/gui.h"
 #include "utl/Logger.h"
 
@@ -86,7 +85,6 @@
 using std::sqrt;
 
 using utl::RSZ;
-using ord::closestPtInRect;
 
 using odb::dbInst;
 using odb::dbPlacementStatus;
@@ -509,13 +507,13 @@
   string buffer_name = makeUniqueInstName("input");
   Instance *parent = db_network_->topInstance();
   Net *buffer_out = makeUniqueNet();
-  Instance *buffer = db_network_->makeInstance(buffer_cell,
-                                               buffer_name.c_str(),
-                                               parent);
+  Instance *buffer = makeInstance(buffer_cell,
+                                  buffer_name.c_str(),
+                                  parent);
   if (buffer) {
     journalMakeBuffer(buffer);
     Point pin_loc = db_network_->location(top_pin);
-    Point buf_loc = core_exists_ ? closestPtInRect(core_, pin_loc) : pin_loc;
+    Point buf_loc = core_exists_ ? core_.closestPtInside(pin_loc) : pin_loc;
     setLocation(buffer, buf_loc);
     designAreaIncr(area(db_network_->cell(buffer_cell)));
     inserted_buffer_count_++;
@@ -544,18 +542,13 @@
 Resizer::setLocation(Instance *inst,
                      Point pt)
 {
-  int x = pt.getX();
-  int y = pt.getY();
   // Stay inside the lines.
-  if (core_exists_) {
-    Point in_core = closestPtInRect(core_, x, y);
-    x = in_core.getX();
-    y = in_core.getY();
-  }
+  if (core_exists_)
+    pt = core_.closestPtInside(pt);
 
   dbInst *dinst = db_network_->staToDb(inst);
   dinst->setPlacementStatus(dbPlacementStatus::PLACED);
-  dinst->setLocation(x, y);
+  dinst->setLocation(pt.getX(), pt.getY());
 }
 
 void
@@ -622,9 +615,9 @@
   string buffer_name = makeUniqueInstName("output");
   Instance *parent = network->topInstance();
   Net *buffer_in = makeUniqueNet();
-  Instance *buffer = network->makeInstance(buffer_cell,
-                                           buffer_name.c_str(),
-                                           parent);
+  Instance *buffer = makeInstance(buffer_cell,
+                                  buffer_name.c_str(),
+                                  parent);
   if (buffer) {
     journalMakeBuffer(buffer);
     setLocation(buffer, db_network_->location(top_pin));
@@ -1330,15 +1323,22 @@
            || load_slew > max_load_slew) {
       // Make the wire a bit shorter than necessary to allow for
       // offset from instance origin to pin and detailed placement movement.
-      double length_margin = .05;
-      int stub_length = std::numeric_limits<int>::max();
-      if (max_length > 0 && wire_length > max_length)
-        stub_length = min(stub_length, max_length);
+      static double length_margin = .05;
+      bool split_wire = false;
+      int split_length = std::numeric_limits<int>::max();
+      if (max_length > 0 && wire_length > max_length) {
+        split_length = min(split_length, max_length);
+        split_wire = true;
+      }
       if (wire_cap > 0.0
           && pin_cap < max_cap
-          && load_cap > max_cap)
-        stub_length = min(stub_length, metersToDbu((max_cap - pin_cap) / wire_cap));
-      if (load_slew > max_load_slew) {
+          && load_cap > max_cap) {
+        split_length = min(split_length, metersToDbu((max_cap - pin_cap) / wire_cap));
+        split_wire = true;
+      }
+      if (load_slew > max_load_slew
+          // Check that zero length wire meets max slew.
+          && r_drvr*pin_cap*k_threshold < max_load_slew) {
         // Using elmore delay to approximate wire
         // load_slew = (Rdrvr + L*Rwire) * (L*Cwire + Cpin) * k_threshold
         // Setting this to max_slew is a quadratic in L
@@ -1348,34 +1348,40 @@
         float b = r_drvr * wire_cap + wire_res * pin_cap;
         float c = r_drvr * pin_cap - max_load_slew / k_threshold;
         float l = (-b + sqrt(b*b - 4 * a * c)) / (2 * a);
-        stub_length = min(stub_length, metersToDbu(l));
+        if (l > 0.0) {
+          split_length = min(split_length, metersToDbu(l));
+          split_wire = true;
+        }
       }
+      if (split_wire) {
+        // Distance from pt to repeater backward toward prev_pt.
+        double buf_dist = length - (wire_length - split_length * (1.0 - length_margin));
+        double dx = prev_x - pt_x;
+        double dy = prev_y - pt_y;
+        double d = (length == 0) ? 0.0 : buf_dist / length;
+        int buf_x = pt_x + d * dx;
+        int buf_y = pt_y + d * dy;
+        makeRepeater("wire", buf_x, buf_y, buffer_lowest_drive_, level,
+                     wire_length, pin_cap, fanout, load_pins);
+        // Update for the next round.
+        length -= buf_dist;
+        wire_length = length;
+        pt_x = buf_x;
+        pt_y = buf_y;
 
-      // Distance from pt to repeater backward toward prev_pt.
-      double buf_dist = length - (wire_length - stub_length * (1.0 - length_margin));
-      double dx = prev_x - pt_x;
-      double dy = prev_y - pt_y;
-      double d = (length == 0) ? 0.0 : buf_dist / length;
-      int buf_x = pt_x + d * dx;
-      int buf_y = pt_y + d * dy;
-      makeRepeater("wire", buf_x, buf_y, buffer_lowest_drive_, level,
-                   wire_length, pin_cap, fanout, load_pins);
-      // Update for the next round.
-      length -= buf_dist;
-      wire_length = length;
-      pt_x = buf_x;
-      pt_y = buf_y;
-
-      wire_length1 = dbuToMeters(wire_length);
-      load_cap = pin_cap + wire_length1 * wire_cap;
-      load_slew = (r_drvr + wire_length1 * wire_res) * load_cap * k_threshold;
-      debugPrint(logger_, RSZ, "repair_net", 3, "{:{}s}load_slew={}",
-                 "", level,
-                 delayAsString(load_slew, this, 3));
-      debugPrint(logger_, RSZ, "repair_net", 3, "{:{}s}wl={} l={}",
-                 "", level,
-                 units_->distanceUnit()->asString(dbuToMeters(wire_length), 1),
-                 units_->distanceUnit()->asString(dbuToMeters(length), 1));
+        wire_length1 = dbuToMeters(wire_length);
+        load_cap = pin_cap + wire_length1 * wire_cap;
+        load_slew = (r_drvr + wire_length1 * wire_res) * load_cap * k_threshold;
+        debugPrint(logger_, RSZ, "repair_net", 3, "{:{}s}load_slew={}",
+                   "", level,
+                   delayAsString(load_slew, this, 3));
+        debugPrint(logger_, RSZ, "repair_net", 3, "{:{}s}wl={} l={}",
+                   "", level,
+                   units_->distanceUnit()->asString(dbuToMeters(wire_length), 1),
+                   units_->distanceUnit()->asString(dbuToMeters(length), 1));
+      }
+      else
+        break;
     }
   }
 }
@@ -1487,9 +1493,9 @@
     }
   }
 
-  Instance *buffer = db_network_->makeInstance(buffer_cell,
-                                               buffer_name.c_str(),
-                                               parent);
+  Instance *buffer = makeInstance(buffer_cell,
+                                  buffer_name.c_str(),
+                                  parent);
   journalMakeBuffer(buffer);
   Point buf_loc(x, y);
   setLocation(buffer, buf_loc);
@@ -2220,8 +2226,8 @@
             Point tie_loc = tieLocation(load, separation_dbu);
             Instance *load_inst = network_->instance(load);
             string tie_name = makeUniqueInstName(inst_name, true);
-            Instance *tie = sta_->makeInstance(tie_name.c_str(),
-                                               tie_cell, top_inst);
+            Instance *tie = makeInstance(tie_cell, tie_name.c_str(),
+                                         top_inst);
             setLocation(tie, tie_loc);
 
             // Make tie output net.
@@ -2318,7 +2324,8 @@
 ////////////////////////////////////////////////////////////////
 
 void
-Resizer::repairSetup(float slack_margin)
+Resizer::repairSetup(float slack_margin,
+                     int max_passes)
 {
   inserted_buffer_count_ = 0;
   resize_count_ = 0;
@@ -2331,7 +2338,8 @@
   int pass = 1;
   int decreasing_slack_passes = 0;
   incrementalParasiticsBegin();
-  while (fuzzyLess(worst_slack, slack_margin)) {
+  while (fuzzyLess(worst_slack, slack_margin)
+         && pass <= max_passes) {
     PathRef worst_path = sta_->vertexWorstSlackPath(worst_vertex, max_);
     bool changed = repairSetup(worst_path, worst_slack);
     updateParasitics();
@@ -2444,154 +2452,89 @@
       int drvr_index = index_delay.first;
       PathRef *drvr_path = expanded.path(drvr_index);
       Vertex *drvr_vertex = drvr_path->vertex(sta_);
-      Pin *drvr_pin = drvr_vertex->pin();
-      PathRef *load_path = expanded.path(drvr_index + 1);
-      Vertex *load_vertex = load_path->vertex(sta_);
-      Pin *load_pin = load_vertex->pin();
+      const Pin *drvr_pin = drvr_vertex->pin();
+      LibertyPort *drvr_port = network_->libertyPort(drvr_pin);
+      LibertyCell *drvr_cell = drvr_port ? drvr_port->libertyCell() : nullptr;
       int fanout = this->fanout(drvr_vertex);
-      debugPrint(logger_, RSZ, "repair_setup", 2, "{} fanout = {}",
+      debugPrint(logger_, RSZ, "repair_setup", 2, "{} {} fanout = {}",
                  network_->pathName(drvr_pin),
+                 drvr_cell ? drvr_cell->name() : "none",
                  fanout);
+
+      if (upsizeDrvr(drvr_path, drvr_index, &expanded)) {
+        changed = true;
+        break;
+      }
+
       // For tristate nets all we can do is resize the driver.
       bool tristate_drvr = isTristateDriver(drvr_pin);
       if (fanout > 1
           // Rebuffer blows up on large fanout nets.
           && fanout < rebuffer_max_fanout_
-          && !tristate_drvr) {
-        int count_before = inserted_buffer_count_;
-        rebuffer(drvr_pin);
-        int insert_count = inserted_buffer_count_ - count_before;
-        if (insert_count > 0) {
+          && !tristate_drvr) { 
+        int rebuffer_count = rebuffer(drvr_pin);
+        if (rebuffer_count > 0) {
           debugPrint(logger_, RSZ, "repair_setup", 2, "rebuffer {} inserted {}",
                      network_->pathName(drvr_pin),
-                     insert_count);
+                     rebuffer_count);
           changed = true;
           break;
         }
       }
+
       // Don't split loads on low fanout nets.
       if (fanout > split_load_min_fanout_
           && !tristate_drvr) {
-        // Divide and conquer.
-        debugPrint(logger_, RSZ, "repair_setup", 2, "split loads {} -> {}",
-                   network_->pathName(drvr_pin),
-                   network_->pathName(load_pin));
-        splitLoads(drvr_path, path_slack);
+        splitLoads(drvr_path, drvr_index, path_slack, &expanded);
         changed = true;
         break;
       }
-      LibertyPort *drvr_port = network_->libertyPort(drvr_pin);
-      float load_cap = graph_delay_calc_->loadCap(drvr_pin, dcalc_ap);
-      int in_index = drvr_index - 1;
-      PathRef *in_path = expanded.path(in_index);
-      Pin *in_pin = in_path->pin(sta_);
-      LibertyPort *in_port = network_->libertyPort(in_pin);
-
-      float prev_drive;
-      if (drvr_index >= 2) {
-        int prev_drvr_index = drvr_index - 2;
-        PathRef *prev_drvr_path = expanded.path(prev_drvr_index);
-        Pin *prev_drvr_pin = prev_drvr_path->pin(sta_);
-        prev_drive = 0.0;
-        LibertyPort *prev_drvr_port = network_->libertyPort(prev_drvr_pin);
-        if (prev_drvr_port) {
-          prev_drive = prev_drvr_port->driveResistance();
-        }
-      }
-      else
-        prev_drive = 0.0;
-      LibertyCell *upsize = upsizeCell(in_port, drvr_port, load_cap,
-                                       prev_drive, dcalc_ap);
-      // DINESH-A: delay cells resize disabled
-      if (upsize && (strncmp(drvr_port->libertyCell()->name(),"sky130_fd_sc_hd__clkdlybuf4s15_2",26) != 0)) {
-        Instance *drvr = network_->instance(drvr_pin);
-	//printf("Dinesh-A: Upsizing the cells: %s %s %s\n",network_->pathName(drvr_pin),drvr_port->libertyCell()->name(),upsize->name());
-        debugPrint(logger_, RSZ, "repair_setup", 2, "resize {} {} -> {}",
-                   network_->pathName(drvr_pin),
-                   drvr_port->libertyCell()->name(),
-                   upsize->name());
-        if (replaceCell(drvr, upsize, true)) {
-          resize_count_++;
-          changed = true;
-          break;
-        }
-      }
     }
   }
   return changed;
 }
 
-void
-Resizer::splitLoads(PathRef *drvr_path,
-                    Slack drvr_slack)
+bool
+Resizer::upsizeDrvr(PathRef *drvr_path,
+                    int drvr_index,
+                    PathExpanded *expanded)
 {
-  Vertex *drvr_vertex = drvr_path->vertex(sta_);
-  const RiseFall *rf = drvr_path->transition(sta_);
-  // Sort fanouts of the drvr on the critical path by slack margin
-  // wrt the critical path slack.
-  vector<pair<Vertex*, Slack>> fanout_slacks;
-  VertexOutEdgeIterator edge_iter(drvr_vertex, graph_);
-  while (edge_iter.hasNext()) {
-    Edge *edge = edge_iter.next();
-    Vertex *fanout_vertex = edge->to(graph_);
-    Slack fanout_slack = sta_->vertexSlack(fanout_vertex, rf, max_);
-    Slack slack_margin = fanout_slack - drvr_slack;
-    debugPrint(logger_, RSZ, "repair_setup", 3, " fanin {} slack_margin = {}",
-               network_->pathName(fanout_vertex->pin()),
-               delayAsString(slack_margin, sta_, 3));
-    fanout_slacks.push_back(pair<Vertex*, Slack>(fanout_vertex, slack_margin));
-  }
+  Pin *drvr_pin = drvr_path->pin(this);
+  const DcalcAnalysisPt *dcalc_ap = drvr_path->dcalcAnalysisPt(sta_);
+  float load_cap = graph_delay_calc_->loadCap(drvr_pin, dcalc_ap);
+  int in_index = drvr_index - 1;
+  PathRef *in_path = expanded->path(in_index);
+  Pin *in_pin = in_path->pin(sta_);
+  LibertyPort *in_port = network_->libertyPort(in_pin);
 
-  sort(fanout_slacks.begin(), fanout_slacks.end(),
-       [](pair<Vertex*, Slack> pair1,
-          pair<Vertex*, Slack> pair2) {
-         return pair1.second > pair2.second;
-       });
-
-  Pin *drvr_pin = drvr_vertex->pin();
-  Net *net = network_->net(drvr_pin);
-
-  string buffer_name = makeUniqueInstName("split");
-  Instance *parent = db_network_->topInstance();
-  LibertyCell *buffer_cell = buffer_lowest_drive_;
-  Instance *buffer = db_network_->makeInstance(buffer_cell,
-                                               buffer_name.c_str(),
-                                               parent);
-  journalMakeBuffer(buffer);
-  inserted_buffer_count_++;
-  designAreaIncr(area(db_network_->cell(buffer_cell)));
-
-  Net *out_net = makeUniqueNet();
-  LibertyPort *input, *output;
-  buffer_cell->bufferPorts(input, output);
-  Point drvr_loc = db_network_->location(drvr_pin);
-  setLocation(buffer, drvr_loc);
-
-  // Split the loads with extra slack to an inserted buffer.
-  // before
-  // drvr_pin -> net -> load_pins
-  // after
-  // drvr_pin -> net -> load_pins with low slack
-  //                 -> buffer_in -> net -> rest of loads
-  sta_->connectPin(buffer, input, net);
-  parasiticsInvalid(net);
-  sta_->connectPin(buffer, output, out_net);
-  int split_index = fanout_slacks.size() / 2;
-  for (int i = 0; i < split_index; i++) {
-    pair<Vertex*, Slack> fanout_slack = fanout_slacks[i];
-    Vertex *load_vertex = fanout_slack.first;
-    Pin *load_pin = load_vertex->pin();
-    // Leave ports connected to original net so verilog port names are preserved.
-    if (!network_->isTopLevelPort(load_pin)) {
-      LibertyPort *load_port = network_->libertyPort(load_pin);
-      Instance *load = network_->instance(load_pin);
-
-      sta_->disconnectPin(load_pin);
-      sta_->connectPin(load, load_port, out_net);
+  float prev_drive;
+  if (drvr_index >= 2) {
+    int prev_drvr_index = drvr_index - 2;
+    PathRef *prev_drvr_path = expanded->path(prev_drvr_index);
+    Pin *prev_drvr_pin = prev_drvr_path->pin(sta_);
+    prev_drive = 0.0;
+    LibertyPort *prev_drvr_port = network_->libertyPort(prev_drvr_pin);
+    if (prev_drvr_port) {
+      prev_drive = prev_drvr_port->driveResistance();
     }
   }
-  Pin *buffer_out_pin = network_->findPin(buffer, output);
-  resizeToTargetSlew(buffer_out_pin, false);
+  else
+    prev_drive = 0.0;
+  LibertyPort *drvr_port = network_->libertyPort(drvr_pin);
+  LibertyCell *upsize = upsizeCell(in_port, drvr_port, load_cap,
+                                   prev_drive, dcalc_ap);
+  if (upsize) {
+    Instance *drvr = network_->instance(drvr_pin);
+    debugPrint(logger_, RSZ, "repair_setup", 2, "resize {} {} -> {}",
+               network_->pathName(drvr_pin),
+               drvr_port->libertyCell()->name(),
+               upsize->name());
+    if (replaceCell(drvr, upsize, true)) {
+      resize_count_++;
+      return true;
+    }
+  }
+  return false;
 }
 
 LibertyCell *
@@ -2634,6 +2577,89 @@
   return nullptr;
 }
 
+void
+Resizer::splitLoads(PathRef *drvr_path,
+                    int drvr_index,
+                    Slack drvr_slack,
+                    PathExpanded *expanded)
+{
+  Pin *drvr_pin = drvr_path->pin(this);
+  PathRef *load_path = expanded->path(drvr_index + 1);
+  Vertex *load_vertex = load_path->vertex(sta_);
+  Pin *load_pin = load_vertex->pin();
+  // Divide and conquer.
+  debugPrint(logger_, RSZ, "repair_setup", 2, "split loads {} -> {}",
+             network_->pathName(drvr_pin),
+             network_->pathName(load_pin));
+
+  Vertex *drvr_vertex = drvr_path->vertex(sta_);
+  const RiseFall *rf = drvr_path->transition(sta_);
+  // Sort fanouts of the drvr on the critical path by slack margin
+  // wrt the critical path slack.
+  vector<pair<Vertex*, Slack>> fanout_slacks;
+  VertexOutEdgeIterator edge_iter(drvr_vertex, graph_);
+  while (edge_iter.hasNext()) {
+    Edge *edge = edge_iter.next();
+    Vertex *fanout_vertex = edge->to(graph_);
+    Slack fanout_slack = sta_->vertexSlack(fanout_vertex, rf, max_);
+    Slack slack_margin = fanout_slack - drvr_slack;
+    debugPrint(logger_, RSZ, "repair_setup", 3, " fanin {} slack_margin = {}",
+               network_->pathName(fanout_vertex->pin()),
+               delayAsString(slack_margin, sta_, 3));
+    fanout_slacks.push_back(pair<Vertex*, Slack>(fanout_vertex, slack_margin));
+  }
+
+  sort(fanout_slacks.begin(), fanout_slacks.end(),
+       [](pair<Vertex*, Slack> pair1,
+          pair<Vertex*, Slack> pair2) {
+         return pair1.second > pair2.second;
+       });
+
+  Net *net = network_->net(drvr_pin);
+
+  string buffer_name = makeUniqueInstName("split");
+  Instance *parent = db_network_->topInstance();
+  LibertyCell *buffer_cell = buffer_lowest_drive_;
+  Instance *buffer = makeInstance(buffer_cell,
+                                  buffer_name.c_str(),
+                                  parent);
+  journalMakeBuffer(buffer);
+  inserted_buffer_count_++;
+  designAreaIncr(area(db_network_->cell(buffer_cell)));
+
+  Net *out_net = makeUniqueNet();
+  LibertyPort *input, *output;
+  buffer_cell->bufferPorts(input, output);
+  Point drvr_loc = db_network_->location(drvr_pin);
+  setLocation(buffer, drvr_loc);
+
+  // Split the loads with extra slack to an inserted buffer.
+  // before
+  // drvr_pin -> net -> load_pins
+  // after
+  // drvr_pin -> net -> load_pins with low slack
+  //                 -> buffer_in -> net -> rest of loads
+  sta_->connectPin(buffer, input, net);
+  parasiticsInvalid(net);
+  sta_->connectPin(buffer, output, out_net);
+  int split_index = fanout_slacks.size() / 2;
+  for (int i = 0; i < split_index; i++) {
+    pair<Vertex*, Slack> fanout_slack = fanout_slacks[i];
+    Vertex *load_vertex = fanout_slack.first;
+    Pin *load_pin = load_vertex->pin();
+    // Leave ports connected to original net so verilog port names are preserved.
+    if (!network_->isTopLevelPort(load_pin)) {
+      LibertyPort *load_port = network_->libertyPort(load_pin);
+      Instance *load = network_->instance(load_pin);
+
+      sta_->disconnectPin(load_pin);
+      sta_->connectPin(load, load_port, out_net);
+    }
+  }
+  Pin *buffer_out_pin = network_->findPin(buffer, output);
+  resizeToTargetSlew(buffer_out_pin, false);
+}
+
 ////////////////////////////////////////////////////////////////
 
 void
@@ -3013,8 +3039,8 @@
     Net *buf_out_net = (i == buffer_count - 1) ? out_net : makeUniqueNet();
     // drvr_pin->drvr_net->hold_buffer->net2->load_pins
     string buffer_name = makeUniqueInstName("hold");
-    buffer = db_network_->makeInstance(buffer_cell, buffer_name.c_str(),
-                                       parent);
+    buffer = makeInstance(buffer_cell, buffer_name.c_str(),
+                          parent);
     journalMakeBuffer(buffer);
     inserted_buffer_count_++;
     designAreaIncr(area(db_network_->cell(buffer_cell)));
@@ -3491,6 +3517,8 @@
                            const Corner *corner)
 {
   LibertyCell *cell = drvr_port->libertyCell();
+  if (db_network_->staToDb(cell) == nullptr)
+    logger_->error(RSZ, 70, "no LEF cell for {}.", cell->name());
   double drvr_r = drvr_port->driveResistance();
   // wire_length1 lower bound
   // wire_length2 upper bound
@@ -3820,8 +3848,8 @@
       Pin *load_pin = load_iter->next();
       if (load_pin != out_pin) {
         string clone_name = makeUniqueInstName(inv_name, true);
-        Instance *clone = sta_->makeInstance(clone_name.c_str(),
-                                             inv_cell, top_inst);
+        Instance *clone = makeInstance(inv_cell, clone_name.c_str(),
+                                       top_inst);
         Point clone_loc = db_network_->location(load_pin);
         journalMakeBuffer(clone);
         setLocation(clone, clone_loc);
@@ -4092,4 +4120,15 @@
   return false;
 }
 
+Instance *Resizer::makeInstance(LibertyCell *cell,
+                                const char *name,
+                                Instance *parent)
+{
+  debugPrint(logger_, RSZ, "make_instance", 1, "make instance {}", name);
+  Instance *inst = db_network_->makeInstance(cell, name, parent);
+  dbInst *db_inst = db_network_->staToDb(inst);
+  db_inst->setSourceType(odb::dbSourceType::TIMING);
+  return inst;
+}
+
 } // namespace
diff --git a/hacks/src/OpenSTA/network/ConcreteNetwork.cc b/hacks/src/OpenSTA/network/ConcreteNetwork.cc
index 8096f2e..e0ee69b 100644
--- a/hacks/src/OpenSTA/network/ConcreteNetwork.cc
+++ b/hacks/src/OpenSTA/network/ConcreteNetwork.cc
@@ -1,5 +1,5 @@
 // OpenSTA, Static Timing Analyzer
-// Copyright (c) 2021, Parallax Software, Inc.
+// Copyright (c) 2022, Parallax Software, Inc.
 // 
 // This program is free software: you can redistribute it and/or modify
 // it under the terms of the GNU General Public License as published by
@@ -8,11 +8,11 @@
 // 
 // This program is distributed in the hope that it will be useful,
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 // GNU General Public License for more details.
 // 
 // You should have received a copy of the GNU General Public License
-// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
 
 #include "ConcreteNetwork.hh"
 
@@ -1192,8 +1192,10 @@
     if (cpin) {
       ConcretePort *pin_cport = reinterpret_cast<ConcretePort*>(cpin->port());
       ConcretePort *cport = ccell->findPort(pin_cport->name());
-      rpins[cport->pinIndex()] = cpin;
-      cpin->port_ = cport;
+      if (cport) {
+        rpins[cport->pinIndex()] = cpin;
+        cpin->port_ = cport;
+      }
     }
   }
   delete [] pins;
@@ -1441,7 +1443,9 @@
 ConcreteNetwork::addConstantNet(Net *net,
 				LogicValue value)
 {
-  constant_nets_[int(value)].insert(net);
+  if (value == LogicValue::zero
+      || value == LogicValue::one)
+    constant_nets_[int(value)].insert(net);
 }
 
 ConstantPinIterator *
diff --git a/hacks/src/OpenSTA/tcl/NetworkEdit.tcl b/hacks/src/OpenSTA/tcl/NetworkEdit.tcl
index bdd4057..9df729d 100644
--- a/hacks/src/OpenSTA/tcl/NetworkEdit.tcl
+++ b/hacks/src/OpenSTA/tcl/NetworkEdit.tcl
@@ -1,5 +1,5 @@
 # OpenSTA, Static Timing Analyzer
-# Copyright (c) 2021, Parallax Software, Inc.
+# Copyright (c) 2022, Parallax Software, Inc.
 # 
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -8,11 +8,11 @@
 # 
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 # 
 # You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
 
 # Network editing commands.
 
diff --git a/hacks/src/OpenSTA/tcl/Sta.tcl b/hacks/src/OpenSTA/tcl/Sta.tcl
index a6e8e34..64f5a08 100644
--- a/hacks/src/OpenSTA/tcl/Sta.tcl
+++ b/hacks/src/OpenSTA/tcl/Sta.tcl
@@ -1,5 +1,5 @@
 # OpenSTA, Static Timing Analyzer
-# Copyright (c) 2021, Parallax Software, Inc.
+# Copyright (c) 2022, Parallax Software, Inc.
 # 
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -8,11 +8,11 @@
 # 
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 # 
 # You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
 
 namespace eval sta {
 
diff --git a/hacks/src/openlane/io_place.py b/hacks/src/openlane/io_place.py
index f3333f6..1e8b2b1 100644
--- a/hacks/src/openlane/io_place.py
+++ b/hacks/src/openlane/io_place.py
@@ -452,7 +452,7 @@
                 pin_bpin = odb.dbBPin_create(bterm)
 
             if(slot < slot_pre):
-                print("ERROR:", "Current Pad:", pin_name, " Slot:" , slot, " is less than Previous One:",slot_pre)
+                print("ERROR:", "Current Pad:", pin_name, " Slot:" , slot, " is less than Previous Slot:",slot_pre)
                 sys.exit(1)
 
             slot_pre = slot
diff --git a/openlane/io_place.py b/openlane/io_place.py
new file mode 100755
index 0000000..86ac1f2
--- /dev/null
+++ b/openlane/io_place.py
@@ -0,0 +1,528 @@
+# Copyright 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.
+
+"""
+Places the IOs according to an input file. Supports regexes.
+File format:
+#N|#S|#E|#W
+pin1_regex
+pin2_regex
+...
+
+#S|#N|#E|#W
+...
+...
+"""
+import odb
+
+import os
+import re
+import sys
+import click
+import random
+
+
+@click.command()
+@click.option("-l", "--input-lef", required=True, help="Input merged tlef/lef file.")
+@click.option(
+    "-o",
+    "--output-def",
+    default="./output.def",
+    help="Output DEF file with newly placed pins",
+)
+@click.option("-c", "--config", required=False, help="Optional configuration file.")
+@click.option(
+    "-r",
+    "--reverse",
+    default="",
+    type=str,
+    help="Reverse along comma,delimited,cardinals: e.g. N,E",
+)
+@click.option("-L", "--length", default=2, type=float, help="Pin length in microns.")
+@click.option(
+    "-V",
+    "--ver-layer",
+    required=True,
+    help="Name of metal layer to place vertical pins on.",
+)
+@click.option(
+    "-H",
+    "--hor-layer",
+    required=True,
+    help="Name of metal layer to place horizontal pins on.",
+)
+@click.option(
+    "--hor-extension",
+    default=0,
+    type=float,
+    help="Extension for vertical pins in microns.",
+)
+@click.option(
+    "--ver-extension",
+    default=0,
+    type=float,
+    help="Extension for horizontal pins in microns.",
+)
+@click.option(
+    "--ver-width-mult", default=2, type=float, help="Multiplier for vertical pins."
+)
+@click.option(
+    "--hor-width-mult", default=2, type=float, help="Multiplier for horizontal pins."
+)
+@click.option(
+    "--bus-sort/--no-bus-sort",
+    default=False,
+    help="Misnomer: pins are grouped by index instead of bus, i.e. a[0] goes with b[0] instead of a[1].",
+)
+@click.argument("input_def")
+def cli(
+    input_lef,
+    output_def,
+    config,
+    ver_layer,
+    hor_layer,
+    ver_width_mult,
+    hor_width_mult,
+    length,
+    hor_extension,
+    ver_extension,
+    reverse,
+    bus_sort,
+    input_def,
+):
+    """
+    Places the IOs in an input def with an optional config file that supports regexes.
+
+    Config format:
+    #N|#S|#E|#W
+    pin1_regex (low co-ordinates to high co-ordinates; e.g., bottom to top and left to right)
+    pin2_regex
+    ...
+
+    #S|#N|#E|#W
+    """
+
+    def_file_name = input_def
+    lef_file_name = input_lef
+    output_def_file_name = output_def
+    config_file_name = config
+    bus_sort_flag = bus_sort
+
+    #1. Manual Pad Placement - Dinesh A
+    manual_place_flag = False 
+
+    h_layer_name = hor_layer
+    v_layer_name = ver_layer
+
+    h_width_mult = float(hor_width_mult)
+    v_width_mult = float(ver_width_mult)
+
+    # Initialize OpenDB
+    db_top = odb.dbDatabase.create()
+    odb.read_lef(db_top, lef_file_name)
+    odb.read_def(db_top, def_file_name)
+    block = db_top.getChip().getBlock()
+
+    micron_in_units = block.getDefUnits()
+
+    LENGTH = int(micron_in_units * length)
+
+    H_EXTENSION = int(micron_in_units * hor_extension)
+    V_EXTENSION = int(micron_in_units * ver_extension)
+
+    if H_EXTENSION < 0:
+        H_EXTENSION = 0
+
+    if V_EXTENSION < 0:
+        V_EXTENSION = 0
+
+    reverse_arr_raw = reverse.split(",")
+    reverse_arr = []
+    for element in reverse_arr_raw:
+        if element.strip() != "":
+            reverse_arr.append(f"#{element}")
+
+    def getGrid(origin, count, step):
+        tracks = []
+        pos = origin
+        for i in range(count):
+            tracks.append(pos)
+            pos += step
+        assert len(tracks) > 0
+        tracks.sort()
+
+        return tracks
+
+    def equallySpacedSeq(m, arr):
+        seq = []
+        n = len(arr)
+        # Bresenham
+        indices = [i * n // m + n // (2 * m) for i in range(m)]
+        for i in indices:
+            seq.append(arr[i])
+        return seq
+
+    # HUMAN SORTING: https://stackoverflow.com/questions/5967500/how-to-correctly-sort-a-string-with-a-number-inside
+    def natural_keys(enum):
+        def atof(text):
+            try:
+                retval = float(text)
+            except ValueError:
+                retval = text
+            return retval
+
+        text = enum[0]
+        text = re.sub(r"(\[|\]|\.|\$)", "", text)
+        """
+        alist.sort(key=natural_keys) sorts in human order
+        http://nedbatchelder.com/blog/200712/human_sorting.html
+        (see toothy's implementation in the comments)
+        float regex comes from https://stackoverflow.com/a/12643073/190597
+        """
+        return [
+            atof(c) for c in re.split(r"[+-]?([0-9]+(?:[.][0-9]*)?|[.][0-9]+)", text)
+        ]
+
+    def bus_keys(enum):
+        text = enum[0]
+        m = re.match(r"^.*\[(\d+)\]$", text)
+        if not m:
+            return -1
+        else:
+            return int(m.group(1))
+
+    #2. Find the Slot matching next nearest slot-DineshA
+    def findSlot(val, arr):
+        for i in arr:
+            if(i > val):
+                return i
+        print("ERROR: Next Valid Position not found :",val)
+        return -1
+
+    # read config
+
+    pin_placement_cfg = {"#N": [], "#E": [], "#S": [], "#W": []}
+    cur_side = None
+    if config_file_name is not None and config_file_name != "":
+        with open(config_file_name, "r") as config_file:
+            for line in config_file:
+                line = line.split()
+                if len(line) == 0:
+                    continue
+
+                #3. Dinesh A - Start
+                if(manual_place_flag == False):
+                    if len(line) > 1:
+                        print("Only one entry allowed per line.")
+                        sys.exit(1)
+                    token = line[0]
+                else:
+                    #During Manual Place we are allowing Four field
+                    # <Pad Name> <Offset> <Position> <Multiplier>
+                    # Causion: Make sure that you have given absolute name, else it will give issue
+                    if len(line) > 4:
+                        print("Only Four entry allowed per line.")
+                        sys.exit(1)
+                    if line[0] not in ["#N", "#E", "#S", "#W", "#NR", "#ER", "#SR", "#WR"]:
+                        token = line
+                    else:
+                        token = line[0]
+
+                if cur_side is not None and token[0] != "#":
+                    pin_placement_cfg[cur_side].append(token)
+                elif token not in [
+                    "#N",
+                    "#E",
+                    "#S",
+                    "#W",
+                    "#NR",
+                    "#ER",
+                    "#SR",
+                    "#WR",
+                    "#BUS_SORT",
+                    "#MANUAL_PLACE"
+                ]:
+                    print(
+                        "Valid directives are #N, #E, #S, or #W. Append R for reversing the default order.",
+                        "Use #BUS_SORT to group 'bus bits' by index.",
+                        "Please make sure you have set a valid side first before listing pins",
+                    )
+                    sys.exit(1)
+                elif token == "#BUS_SORT":
+                    bus_sort_flag = True
+                #4 - Dinesh A
+                elif token == "#MANUAL_PLACE":
+                    print("Input token ",token)
+                    manual_place_flag = True
+                else:
+                    if len(token) == 3:
+                        token = token[0:2]
+                        reverse_arr.append(token)
+                    cur_side = token
+
+    # build a list of pins
+
+    chip_top = db_top.getChip()
+    block_top = chip_top.getBlock()
+    top_design_name = block_top.getName()
+    tech = db_top.getTech()
+
+    H_LAYER = tech.findLayer(h_layer_name)
+    V_LAYER = tech.findLayer(v_layer_name)
+
+    H_WIDTH = int(h_width_mult * H_LAYER.getWidth())
+    V_WIDTH = int(v_width_mult * V_LAYER.getWidth())
+
+    print("Top-level design name:", top_design_name)
+
+    bterms = block_top.getBTerms()
+    bterms_enum = []
+    for bterm in bterms:
+        pin_name = bterm.getName()
+        bterms_enum.append((pin_name, bterm))
+
+    # sort them "humanly"
+    bterms_enum.sort(key=natural_keys)
+    if bus_sort_flag:
+        bterms_enum.sort(key=bus_keys)
+    bterms = [bterm[1] for bterm in bterms_enum]
+
+    pin_placement = {"#N": [], "#E": [], "#S": [], "#W": []}
+    bterm_regex_map = {}
+    #5. Dinesh A
+    if(manual_place_flag == False):
+	    for side in pin_placement_cfg:
+                for regex in pin_placement_cfg[side]:  # going through them in order
+                    regex += "$"  # anchor
+                    for bterm in bterms:
+                        # if a pin name matches multiple regexes, their order will be
+                        # arbitrary. More refinement requires more strict regexes (or just
+                        # the exact pin name).
+                        pin_name = bterm.getName()
+                        if re.match(regex, pin_name) is not None:
+                            if bterm in bterm_regex_map:
+                                print(
+		                    "Error: Multiple regexes matched",
+		                    pin_name,
+		                    ". Those are",
+		                    bterm_regex_map[bterm],
+		                    "and",
+		                    regex,
+		                )
+                                sys.exit(os.EX_DATAERR)
+                            bterm_regex_map[bterm] = regex
+                            pin_placement[side].append(bterm)  # to maintain the order
+
+	    unmatched_bterms = [bterm for bterm in bterms if bterm not in bterm_regex_map]
+
+	    if len(unmatched_bterms) > 0:
+                print("Warning: Some pins weren't matched by the config file")
+                print("Those are:", [bterm.getName() for bterm in unmatched_bterms])
+                if True:
+                    print("Assigning random sides to the above pins")
+                    for bterm in unmatched_bterms:
+                        random_side = random.choice(list(pin_placement.keys()))
+                        pin_placement[random_side].append(bterm)
+                else:
+                    sys.exit(1)
+
+    #6 Dinesh A
+    else:
+	    for side in pin_placement_cfg:
+                for regex in pin_placement_cfg[side]:  # going through them in order
+                    regex = regex[0]  # take first value
+                    regex += "$"  # anchor
+                    for bterm in bterms:
+		        # if a pin name matches multiple regexes, their order will be
+		        # arbitrary. More refinement requires more strict regexes (or just
+		        # the exact pin name).
+                        pin_name = bterm.getName()
+                        if re.match(regex, pin_name) is not None:
+                            print("Debug: Serching Pin match",regex)
+                            if bterm in bterm_regex_map:
+                                #print("Warning: Multiple regexes matched", pin_name)
+                                #      ". Those are", bterm_regex_map[bterm], "and", regex)
+                                sys.exit(1)
+                            bterm_regex_map[bterm] = regex
+                            pin_placement[side].append(bterm)  # to maintain the order
+	    
+	    unmatched_bterms = [bterm for bterm in bterms if bterm not in bterm_regex_map]
+	    
+	    if len(unmatched_bterms) > 0:
+                print("Warning: Some pins weren't matched by the config file")
+                print("Those are:", [bterm.getName() for bterm in unmatched_bterms])
+                sys.exit(1)
+
+
+    assert len(block_top.getBTerms()) == len(
+        pin_placement["#N"]
+        + pin_placement["#E"]
+        + pin_placement["#S"]
+        + pin_placement["#W"]
+    )
+
+    # generate slots
+
+    DIE_AREA = block_top.getDieArea()
+    BLOCK_LL_X = DIE_AREA.xMin()
+    BLOCK_LL_Y = DIE_AREA.yMin()
+    BLOCK_UR_X = DIE_AREA.xMax()
+    BLOCK_UR_Y = DIE_AREA.yMax()
+
+    print("Block boundaries:", BLOCK_LL_X, BLOCK_LL_Y, BLOCK_UR_X, BLOCK_UR_Y)
+
+    origin, count, step = block_top.findTrackGrid(H_LAYER).getGridPatternY(0)
+
+    #7. Save the horizontal origin and step - DineshA
+    h_origin = origin
+    h_step   = step
+
+    h_tracks = getGrid(origin, count, step)
+
+    origin, count, step = block_top.findTrackGrid(V_LAYER).getGridPatternX(0)
+
+    #8. Save the horizontal origin and step - DineshA
+    v_origin = origin
+    v_step   = step
+
+    v_tracks = getGrid(origin, count, step)
+
+    for rev in reverse_arr:
+        pin_placement[rev].reverse()
+
+    # create the pins
+    #9.  DineshA
+    if(manual_place_flag == False):
+	    for side in pin_placement:
+                if side in ["#N", "#S"]:
+                    slots = equallySpacedSeq(len(pin_placement[side]), v_tracks)
+                else:
+                    slots = equallySpacedSeq(len(pin_placement[side]), h_tracks)
+
+                assert len(slots) == len(pin_placement[side])
+
+                for i in range(len(pin_placement[side])):
+                    bterm = pin_placement[side][i]
+                    slot = slots[i]
+
+                    pin_name = bterm.getName()
+                    pins = bterm.getBPins()
+                    if len(pins) > 0:
+                        print("Warning:", pin_name, "already has shapes. Modifying them")
+                        assert len(pins) == 1
+                        pin_bpin = pins[0]
+                    else:
+                        pin_bpin = odb.dbBPin_create(bterm)
+
+                    pin_bpin.setPlacementStatus("PLACED")
+
+                    if side in ["#N", "#S"]:
+                        rect = odb.Rect(0, 0, V_WIDTH, LENGTH + V_EXTENSION)
+                        if side == "#N":
+                            y = BLOCK_UR_Y - LENGTH
+                        else:
+                            y = BLOCK_LL_Y - V_EXTENSION
+                        rect.moveTo(slot - V_WIDTH // 2, y)
+                        odb.dbBox_create(pin_bpin, V_LAYER, *rect.ll(), *rect.ur())
+                    else:
+                        rect = odb.Rect(0, 0, LENGTH + H_EXTENSION, H_WIDTH)
+                        if side == "#E":
+                            x = BLOCK_UR_X - LENGTH
+                        else:
+                            x = BLOCK_LL_X - H_EXTENSION
+                        rect.moveTo(x, slot - H_WIDTH // 2)
+                        odb.dbBox_create(pin_bpin, H_LAYER, *rect.ll(), *rect.ur())
+
+    else:
+	    #10.New Logic, Manual Pin Placement - Dinesh A
+	    #print("Allowed VTracks",v_tracks)
+	    #print("Allowed hTracks",h_tracks)
+
+	    for side in pin_placement:
+
+                if(len(pin_placement[side]) != len(pin_placement_cfg[side])):
+                    print("ERROR : At Side:", side, " Total Pin Defined ",len(pin_placement_cfg[side]), "More than available:",len(pin_placement[side]))
+		    
+		#check defined pad are more than avaibale one
+                assert len(pin_placement[side]) == len(pin_placement_cfg[side])
+                start = 0
+	    
+                start_loc = 0
+                pad_pos   = 0
+                slot_pre = 0
+                #Dinesh: Give Step Multipler size *2  for better pad placement
+                multiplier= 2
+                for i in range(len(pin_placement_cfg[side])):
+                    #Dinesh: Multiply the offset by 1000 for micro conversion
+                    if(len(pin_placement_cfg[side][i]) > 1):
+                        start_loc = int(pin_placement_cfg[side][i][1])
+                    if(len(pin_placement_cfg[side][i]) > 2):
+                        pad_pos   = int(pin_placement_cfg[side][i][2])
+                    if(len(pin_placement_cfg[side][i]) > 3):
+                        multiplier = int(pin_placement_cfg[side][i][3])
+
+                    if side in ["#N", "#S"]:
+                        slott = start_loc*1000+int(v_origin)+(int(v_step) * pad_pos * multiplier)
+                        slot =findSlot(slott,v_tracks)
+                    else:
+                        slott = start_loc*1000+int(h_origin)+(int(h_step) * pad_pos * multiplier)
+                        slot =findSlot(slott,h_tracks)
+		  
+                    pad_pos +=1
+                    bterm = pin_placement[side][i]
+	    
+                    pin_name = bterm.getName()
+                    pins = bterm.getBPins()
+                    if len(pins) > 0:
+                        print("Warning:", pin_name, "already has shapes. Modifying them")
+                        assert len(pins) == 1
+                        pin_bpin = pins[0]
+                    else:
+                        pin_bpin = odb.dbBPin_create(bterm)
+
+                    if(slot < slot_pre):
+                        print("ERROR:", "Current Pad:", pin_name, " Slot:" , slot, " is less than Previous One:",slot_pre)
+                        sys.exit(1)
+
+                    slot_pre = slot
+
+                    print("Dinesh: Placing Pad:" ,pin_name, " At Side: ", side, " Slot: ", slot)
+                    pin_bpin.setPlacementStatus("PLACED")
+	    
+                    if side in ["#N", "#S"]:
+                        rect = odb.Rect(0, 0, V_WIDTH, LENGTH+V_EXTENSION)
+                        if side == "#N":
+                            y = BLOCK_UR_Y-LENGTH
+                        else:
+                            y = BLOCK_LL_Y-V_EXTENSION
+                        rect.moveTo(slot-V_WIDTH//2, y)
+                        odb.dbBox_create(pin_bpin, V_LAYER, *rect.ll(), *rect.ur())
+                    else:
+                        rect = odb.Rect(0, 0, LENGTH+H_EXTENSION, H_WIDTH)
+                        if side == "#E":
+                            x = BLOCK_UR_X-LENGTH
+                        else:
+                            x = BLOCK_LL_X-H_EXTENSION
+                        rect.moveTo(x, slot-H_WIDTH//2)
+                        odb.dbBox_create(pin_bpin, H_LAYER, *rect.ll(), *rect.ur())
+
+
+    print(
+        f"Writing {output_def_file_name}...",
+    )
+    odb.write_def(block_top, output_def_file_name)
+
+
+if __name__ == "__main__":
+    cli()
diff --git a/openlane/user_project_wrapper/config.tcl b/openlane/user_project_wrapper/config.tcl
index ae4bbce..2895ec4 100644
--- a/openlane/user_project_wrapper/config.tcl
+++ b/openlane/user_project_wrapper/config.tcl
@@ -44,7 +44,6 @@
 
 ## Source Verilog Files
 set ::env(VERILOG_FILES) "\
-	$proj_dir/../../caravel/verilog/rtl/defines.v \
 	$proj_dir/../../verilog/rtl/user_project_wrapper.v"
 
 ## Clock configurations
@@ -58,7 +57,7 @@
 set ::env(FP_SIZING) "absolute"
 set ::env(MACRO_PLACEMENT_CFG) $proj_dir/macro.cfg
 
-#set ::env(PDN_CFG) $proj_dir/pdn.tcl
+set ::env(PDN_CFG) $proj_dir/pdn_cfg.tcl
 
 set ::env(SDC_FILE) "$proj_dir/base.sdc"
 set ::env(BASE_SDC_FILE) "$proj_dir/base.sdc"
@@ -101,7 +100,7 @@
 
 set ::env(SYNTH_DEFINES) [list SYNTHESIS ]
 
-set ::env(VERILOG_INCLUDE_DIRS) [glob $proj_dir/../../verilog/rtl/yifive/ycr1c/src/includes ]
+#set ::env(VERILOG_INCLUDE_DIRS) [glob $proj_dir/../../verilog/rtl/yifive/ycr1c/src/includes ]
 
 set ::env(GLB_RT_MAXLAYER) 5
 
@@ -116,8 +115,8 @@
 set ::env(VDD_NETS) "vccd1 vccd2 vdda1 vdda2"
 set ::env(GND_NETS) "vssd1 vssd2 vssa1 vssa2"
 #
-set ::env(VDD_PIN) "vccd1 vccd2 vdda1 vdda2"
-set ::env(GND_PIN) "vssd1 vssd2 vssa1 vssa2"
+set ::env(VDD_PIN) "vccd1"
+set ::env(GND_PIN) "vssd1"
 
 set ::env(GLB_RT_OBS) " li1   150 2100  833.1  2516.54,\
 	                met1  150 2100  833.1  2516.54,\
@@ -149,7 +148,9 @@
                         met3 150  200  833.1   616.54,\
 	                met5  0 0 2920 3520"
 
-set ::env(FP_PDN_MACRO_HOOKS) "\
+set ::env(FP_PDN_POWER_STRAPS) "vccd1 vssd1 1, vccd2 vssd2 0, vdda1 vssa1 0, vdda2 vssa2 0"
+
+set ::env(FP_PDN_MACRO_HOOKS) " \
 	u_intercon vccd1 vssd1 \
 	u_pinmux vccd1 vssd1 \
 	u_qspi_master vccd1 vssd1 \
@@ -163,8 +164,7 @@
 	u_sram2_2kb vccd1 vssd1 \
 	u_sram3_2kb vccd1 vssd1 \
 	u_uart_i2c_usb_spi vccd1 vssd1 \
-	u_wb_host vccd1 vssd1 \
-	"
+	u_wb_host vccd1 vssd1 "
 
 
 # The following is because there are no std cells in the example wrapper project.
@@ -190,9 +190,19 @@
 set ::env(QUIT_ON_TIMING_VIOLATIONS) "0"
 set ::env(QUIT_ON_TR_DRC) "0"
 
+set ::env(FP_PDN_IRDROP) "1"
+set ::env(FP_PDN_HORIZONTAL_HALO) "10"
+set ::env(FP_PDN_VERTICAL_HALO) "10"
 
-set ::env(FP_PDN_HPITCH) "90"
-set ::env(FP_PDN_VPITCH) "100"
-set ::env(FP_PDN_HSPACING) "6"
+set ::env(FP_PDN_VOFFSET) "5"
+set ::env(FP_PDN_VPITCH) "80"
+set ::env(FP_PDN_VSPACING) "15.5"
+set ::env(FP_PDN_VWIDTH) "3.1"
+
+set ::env(FP_PDN_HOFFSET) "10"
+set ::env(FP_PDN_HPITCH) "120"
+set ::env(FP_PDN_HSPACING) "10"
+set ::env(FP_PDN_HWIDTH) "3.1"
+
 
 
diff --git a/openlane/user_project_wrapper/interactive.mpw4.tcl b/openlane/user_project_wrapper/interactive.mpw4.tcl
new file mode 100644
index 0000000..1c24305
--- /dev/null
+++ b/openlane/user_project_wrapper/interactive.mpw4.tcl
@@ -0,0 +1,394 @@
+#!/usr/bin/tclsh
+# SPDX-FileCopyrightText: 2020 Efabless Corporation
+# 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.
+# SPDX-License-Identifier: Apache-2.0
+
+package require openlane;
+
+
+proc run_placement_step {args} {
+    if { ! [ info exists ::env(PLACEMENT_CURRENT_DEF) ] } {
+        set ::env(PLACEMENT_CURRENT_DEF) $::env(CURRENT_DEF)
+    } else {
+        set ::env(CURRENT_DEF) $::env(PLACEMENT_CURRENT_DEF)
+    }
+
+    run_placement
+}
+
+proc run_placement {args} {
+	puts_info "Running Placement..."
+# |----------------------------------------------------|
+# |----------------   3. PLACEMENT   ------------------|
+# |----------------------------------------------------|
+	set ::env(CURRENT_STAGE) placement
+
+    if { [info exists ::env(PL_TARGET_DENSITY_CELLS)] } {
+        set old_pl_target_density $::env(PL_TARGET_DENSITY)
+        set ::env(PL_TARGET_DENSITY) $::env(PL_TARGET_DENSITY_CELLS)
+    }
+
+    if { $::env(PL_RANDOM_GLB_PLACEMENT) } {
+        # useful for very tiny designs
+        random_global_placement
+    } else {
+        global_placement_or
+    }
+
+    if { [info exists ::env(PL_TARGET_DENSITY_CELLS)] } {
+        set ::env(PL_TARGET_DENSITY) $old_pl_target_density
+    }
+
+    run_resizer_design
+
+    if { [info exists ::env(DONT_BUFFER_PORTS) ]} {
+        remove_buffers
+    }
+    detailed_placement_or
+    scrot_klayout -layout $::env(CURRENT_DEF)
+}
+
+
+proc run_cts_step {args} {
+    if { ! [ info exists ::env(CTS_CURRENT_DEF) ] } {
+        set ::env(CTS_CURRENT_DEF) $::env(CURRENT_DEF)
+    } else {
+        set ::env(CURRENT_DEF) $::env(CTS_CURRENT_DEF)
+    }
+
+    run_cts
+    run_resizer_timing
+}
+
+proc run_routing_step {args} {
+    if { ! [ info exists ::env(ROUTING_CURRENT_DEF) ] } {
+        set ::env(ROUTING_CURRENT_DEF) $::env(CURRENT_DEF)
+    } else {
+        set ::env(CURRENT_DEF) $::env(ROUTING_CURRENT_DEF)
+    }
+    run_routing
+}
+
+proc run_diode_insertion_2_5_step {args} {
+    if { ! [ info exists ::env(DIODE_INSERTION_CURRENT_DEF) ] } {
+        set ::env(DIODE_INSERTION_CURRENT_DEF) $::env(CURRENT_DEF)
+    } else {
+        set ::env(CURRENT_DEF) $::env(DIODE_INSERTION_CURRENT_DEF)
+    }
+	if { ($::env(DIODE_INSERTION_STRATEGY) == 2) || ($::env(DIODE_INSERTION_STRATEGY) == 5) } {
+		run_antenna_check
+		heal_antenna_violators; # modifies the routed DEF
+	}
+
+}
+
+proc run_power_pins_insertion_step {args} {
+    # set_def $::env(tritonRoute_result_file_tag).def
+    if { ! [ info exists ::env(POWER_PINS_INSERTION_CURRENT_DEF) ] } {
+        set ::env(POWER_PINS_INSERTION_CURRENT_DEF) $::env(CURRENT_DEF)
+    } else {
+        set ::env(CURRENT_DEF) $::env(POWER_PINS_INSERTION_CURRENT_DEF)
+    }
+    if { $::env(LVS_INSERT_POWER_PINS) } {
+		write_powered_verilog
+		set_netlist $::env(lvs_result_file_tag).powered.v
+    }
+
+}
+
+
+proc run_lvs_step {{ lvs_enabled 1 }} {
+    if { ! [ info exists ::env(LVS_CURRENT_DEF) ] } {
+        set ::env(LVS_CURRENT_DEF) $::env(CURRENT_DEF)
+    } else {
+        set ::env(CURRENT_DEF) $::env(LVS_CURRENT_DEF)
+    }
+	if { $lvs_enabled } {
+		run_magic_spice_export
+		run_lvs; # requires run_magic_spice_export
+	}
+
+}
+
+proc run_drc_step {{ drc_enabled 1 }} {
+    if { ! [ info exists ::env(DRC_CURRENT_DEF) ] } {
+        set ::env(DRC_CURRENT_DEF) $::env(CURRENT_DEF)
+    } else {
+        set ::env(CURRENT_DEF) $::env(DRC_CURRENT_DEF)
+    }
+	if { $drc_enabled } {
+		run_magic_drc
+		run_klayout_drc
+	}
+}
+
+proc run_antenna_check_step {{ antenna_check_enabled 1 }} {
+    if { ! [ info exists ::env(ANTENNA_CHECK_CURRENT_DEF) ] } {
+        set ::env(ANTENNA_CHECK_CURRENT_DEF) $::env(CURRENT_DEF)
+    } else {
+        set ::env(CURRENT_DEF) $::env(ANTENNA_CHECK_CURRENT_DEF)
+    }
+	if { $antenna_check_enabled } {
+		run_antenna_check
+	}
+}
+
+
+proc gen_pdn {args} {
+    puts_info "Generating PDN..."
+    TIMER::timer_start
+	
+    set ::env(SAVE_DEF) [index_file $::env(pdn_tmp_file_tag).def]
+    set ::env(PGA_RPT_FILE) [index_file $::env(pdn_report_file_tag).pga.rpt]
+
+    try_catch $::env(OPENROAD_BIN) -exit $::env(SCRIPTS_DIR)/openroad/pdn.tcl \
+	|& tee $::env(TERMINAL_OUTPUT) [index_file $::env(pdn_log_file_tag).log 0]
+
+
+    TIMER::timer_stop
+    exec echo "[TIMER::get_runtime]" >> [index_file $::env(pdn_log_file_tag)_runtime.txt 0]
+
+    quit_on_unconnected_pdn_nodes
+
+    set_def $::env(SAVE_DEF)
+}
+
+proc run_power_grid_generation {args} {
+
+	if {[info exists ::env(FP_PDN_POWER_STRAPS)]} {
+	     set power_domains [split $::env(FP_PDN_POWER_STRAPS) ","]
+	}
+
+	# internal macros power connections 
+	if {[info exists ::env(FP_PDN_MACRO_HOOKS)]} {
+		set macro_hooks [dict create]
+		set pdn_hooks [split $::env(FP_PDN_MACRO_HOOKS) ","]
+		foreach pdn_hook $pdn_hooks {
+			set instance_name [lindex $pdn_hook 0]
+			set power_net [lindex $pdn_hook 1]
+			set ground_net [lindex $pdn_hook 2]
+			dict append macro_hooks $instance_name [subst {$power_net $ground_net}]
+		}
+		
+		set power_net_indx [lsearch $::env(VDD_NETS) $power_net]
+		set ground_net_indx [lsearch $::env(GND_NETS) $ground_net]
+
+		# make sure that the specified power domains exist.
+		if { $power_net_indx == -1  || $ground_net_indx == -1 || $power_net_indx != $ground_net_indx } {
+			puts_err "Can't find $power_net and $ground_net domain. \
+			Make sure that both exist in $::env(VDD_NETS) and $::env(GND_NETS)." 
+		} 
+	}
+	
+	# generate multiple power grids per pair of (VDD,GND)
+	# offseted by WIDTH + SPACING
+	foreach domain $power_domains {
+		set ::env(VDD_NET)       [lindex $domain 0]
+	        set ::env(GND_NET)       [lindex $domain 1]
+	        set ::env(_WITH_STRAPS)  [lindex $domain 2]
+
+	        puts_info "Connecting Power: $::env(VDD_NET) & $::env(GND_NET) to All internal macros."
+		# internal macros power connections
+		set ::env(FP_PDN_MACROS) ""
+		if { $::env(FP_PDN_ENABLE_MACROS_GRID) == 1 } {
+			# if macros connections to power are explicitly set
+			# default behavoir macro pins will be connected to the first power domain
+			if { [info exists ::env(FP_PDN_MACRO_HOOKS)] } {
+				set ::env(FP_PDN_ENABLE_MACROS_GRID) 0
+				foreach {instance_name hooks} $macro_hooks {
+					set power [lindex $hooks 0]
+					set ground [lindex $hooks 1]			 
+					if { $power == $::env(VDD_NET) && $ground == $::env(GND_NET) } {
+						set ::env(FP_PDN_ENABLE_MACROS_GRID) 1
+                                                set ::env(FP_PDN_IRDROP) "1"
+						puts_info "Connecting $instance_name to $power and $ground nets."
+						lappend ::env(FP_PDN_MACROS) $instance_name
+					}
+				}
+			} 
+		} else {
+			puts_warn "All internal macros will not be connected to power $::env(VDD_NET) & $::env(GND_NET)."
+		}
+		
+		gen_pdn
+
+		set ::env(FP_PDN_ENABLE_RAILS) 0
+		set ::env(FP_PDN_ENABLE_MACROS_GRID) 0
+                set ::env(FP_PDN_IRDROP) "0"
+
+		# allow failure until open_pdks is up to date...
+		catch {set ::env(FP_PDN_VOFFSET) [expr $::env(FP_PDN_VOFFSET)+$::env(FP_PDN_VWIDTH)+$::env(FP_PDN_VSPACING)]}
+		catch {set ::env(FP_PDN_HOFFSET) [expr $::env(FP_PDN_HOFFSET)+$::env(FP_PDN_HWIDTH)+$::env(FP_PDN_HSPACING)]}
+
+		catch {set ::env(FP_PDN_CORE_RING_VOFFSET) \
+			[expr $::env(FP_PDN_CORE_RING_VOFFSET)\
+			+2*($::env(FP_PDN_CORE_RING_VWIDTH)\
+			+max($::env(FP_PDN_CORE_RING_VSPACING), $::env(FP_PDN_CORE_RING_HSPACING)))]}
+		catch {set ::env(FP_PDN_CORE_RING_HOFFSET) [expr $::env(FP_PDN_CORE_RING_HOFFSET)\
+			+2*($::env(FP_PDN_CORE_RING_HWIDTH)+\
+			max($::env(FP_PDN_CORE_RING_VSPACING), $::env(FP_PDN_CORE_RING_HSPACING)))]}
+		puts "FP_PDN_VOFFSET: $::env(FP_PDN_VOFFSET)"
+		puts "FP_PDN_HOFFSET: $::env(FP_PDN_HOFFSET)"
+		puts "FP_PDN_CORE_RING_VOFFSET: $::env(FP_PDN_CORE_RING_VOFFSET)"
+		puts "FP_PDN_CORE_RING_HOFFSET: $::env(FP_PDN_CORE_RING_HOFFSET)"
+
+	}
+	set ::env(FP_PDN_ENABLE_RAILS) 1
+}
+
+
+proc run_floorplan {args} {
+		puts_info "Running Floorplanning..."
+		# |----------------------------------------------------|
+		# |----------------   2. FLOORPLAN   ------------------|
+		# |----------------------------------------------------|
+		#
+		# intial fp
+		init_floorplan
+
+
+		# place io
+		if { [info exists ::env(FP_PIN_ORDER_CFG)] } {
+				place_io_ol
+		} else {
+			if { [info exists ::env(FP_CONTEXT_DEF)] && [info exists ::env(FP_CONTEXT_LEF)] } {
+				place_io
+				global_placement_or
+				place_contextualized_io \
+					-lef $::env(FP_CONTEXT_LEF) \
+					-def $::env(FP_CONTEXT_DEF)
+			} else {
+				place_io
+			}
+		}
+
+		apply_def_template
+
+		if { [info exist ::env(EXTRA_LEFS)] } {
+			if { [info exist ::env(MACRO_PLACEMENT_CFG)] } {
+				file copy -force $::env(MACRO_PLACEMENT_CFG) $::env(TMP_DIR)/macro_placement.cfg
+				manual_macro_placement f
+			} else {
+				global_placement_or
+				basic_macro_placement
+			}
+		}
+
+		# tapcell
+		tap_decap_or
+		scrot_klayout -layout $::env(CURRENT_DEF)
+		# power grid generation
+		run_power_grid_generation
+}
+
+
+proc run_flow {args} {
+       set script_dir [file dirname [file normalize [info script]]]
+
+		set options {
+		{-design required}
+		{-save_path optional}
+		{-no_lvs optional}
+	    {-no_drc optional}
+	    {-no_antennacheck optional}
+	}
+	set flags {-save}
+	parse_key_args "run_flow" args arg_values $options flags_map $flags -no_consume
+
+	prep {*}$args
+
+        set LVS_ENABLED 1
+        set DRC_ENABLED 0
+        set ANTENNACHECK_ENABLED 1
+
+        set steps [dict create \
+		"synthesis" {run_synthesis "" } \
+                "floorplan" {run_floorplan ""} \
+                "placement" {run_placement_step ""} \
+                "cts" {run_cts_step ""} \
+                "routing" {run_routing_step ""} \
+                "diode_insertion" {run_diode_insertion_2_5_step ""} \
+                "power_pins_insertion" {run_power_pins_insertion_step ""} \
+                "gds_magic" {run_magic ""} \
+                "gds_drc_klayout" {run_klayout ""} \
+                "gds_xor_klayout" {run_klayout_gds_xor ""} \
+                "lvs" "run_lvs_step $LVS_ENABLED" \
+                "drc" "run_drc_step $DRC_ENABLED" \
+                "antenna_check" "run_antenna_check_step $ANTENNACHECK_ENABLED" \
+                "cvc" {run_lef_cvc}
+        ]
+
+       set_if_unset arg_values(-to) "cvc";
+
+       if {  [info exists ::env(CURRENT_STEP) ] } {
+           puts "\[INFO\]:Picking up where last execution left off"
+           puts [format "\[INFO\]:Current stage is %s " $::env(CURRENT_STEP)]
+       } else {
+           set ::env(CURRENT_STEP) "synthesis";
+       }
+       set_if_unset arg_values(-from) $::env(CURRENT_STEP);
+       set exe 0;
+       dict for {step_name step_exe} $steps {
+           if { [ string equal $arg_values(-from) $step_name ] } {
+               set exe 1;
+           }
+
+           if { $exe } {
+               # For when it fails
+               set ::env(CURRENT_STEP) $step_name
+               [lindex $step_exe 0] [lindex $step_exe 1] ;
+           }
+
+           if { [ string equal $arg_values(-to) $step_name ] } {
+               set exe 0:
+               break;
+           }
+
+       }
+
+       # for when it resumes
+       set steps_as_list [dict keys $steps]
+       set next_idx [expr [lsearch $steps_as_list $::env(CURRENT_STEP)] + 1]
+       set ::env(CURRENT_STEP) [lindex $steps_as_list $next_idx]
+
+	if {  [info exists flags_map(-save) ] } {
+		if { ! [info exists arg_values(-save_path)] } {
+			set arg_values(-save_path) ""
+		}
+		save_views 	-lef_path $::env(magic_result_file_tag).lef \
+			-def_path $::env(CURRENT_DEF) \
+			-gds_path $::env(magic_result_file_tag).gds \
+			-mag_path $::env(magic_result_file_tag).mag \
+			-maglef_path $::env(magic_result_file_tag).lef.mag \
+			-spice_path $::env(magic_result_file_tag).spice \
+			-spef_path $::env(CURRENT_SPEF) \
+			-verilog_path $::env(CURRENT_NETLIST) \
+			-save_path $arg_values(-save_path) \
+			-tag $::env(RUN_TAG)
+	}
+
+
+	calc_total_runtime
+	save_state
+	generate_final_summary_report
+	
+	check_timing_violations
+
+	puts_success "Flow Completed Without Fatal Errors."
+
+}
+
+run_flow {*}$argv
diff --git a/openlane/user_project_wrapper/interactive.tcl b/openlane/user_project_wrapper/interactive.tcl
index 61f45ea..45668cd 100644
--- a/openlane/user_project_wrapper/interactive.tcl
+++ b/openlane/user_project_wrapper/interactive.tcl
@@ -18,7 +18,6 @@
 
 package require openlane;
 
-
 proc run_placement_step {args} {
     if { ! [ info exists ::env(PLACEMENT_CURRENT_DEF) ] } {
         set ::env(PLACEMENT_CURRENT_DEF) $::env(CURRENT_DEF)
@@ -62,21 +61,6 @@
 
 }
 
-proc run_power_pins_insertion_step {args} {
-    # set_def $::env(tritonRoute_result_file_tag).def
-    if { ! [ info exists ::env(POWER_PINS_INSERTION_CURRENT_DEF) ] } {
-        set ::env(POWER_PINS_INSERTION_CURRENT_DEF) $::env(CURRENT_DEF)
-    } else {
-        set ::env(CURRENT_DEF) $::env(POWER_PINS_INSERTION_CURRENT_DEF)
-    }
-    if { $::env(LVS_INSERT_POWER_PINS) } {
-		write_powered_verilog
-		set_netlist $::env(lvs_result_file_tag).powered.v
-    }
-
-}
-
-
 proc run_lvs_step {{ lvs_enabled 1 }} {
     if { ! [ info exists ::env(LVS_CURRENT_DEF) ] } {
         set ::env(LVS_CURRENT_DEF) $::env(CURRENT_DEF)
@@ -113,100 +97,341 @@
 	}
 }
 
+proc run_eco_step {args} {
+	if {  $::env(ECO_ENABLE) == 1 } {
+        run_eco
+    }
+}
+
+proc save_final_views {args} {
+	set options {
+		{-save_path optional}
+	}
+	set flags {}
+	parse_key_args "save_final_views" args arg_values $options flags_map $flags
+
+	set arg_list [list]
+
+	# If they don't exist, save_views will simply not copy them
+	lappend arg_list -lef_path $::env(finishing_results)/$::env(DESIGN_NAME).lef
+	lappend arg_list -gds_path $::env(finishing_results)/$::env(DESIGN_NAME).gds
+	lappend arg_list -mag_path $::env(finishing_results)/$::env(DESIGN_NAME).mag
+	lappend arg_list -maglef_path $::env(finishing_results)/$::env(DESIGN_NAME).lef.mag
+	lappend arg_list -spice_path $::env(finishing_results)/$::env(DESIGN_NAME).spice
+	
+	# Guaranteed to have default values
+	lappend arg_list -def_path $::env(CURRENT_DEF)
+	lappend arg_list -verilog_path $::env(CURRENT_NETLIST)
+
+	# Not guaranteed to have default values
+	if { [info exists ::env(SPEF_TYPICAL)] } {
+		lappend arg_list -spef_path $::env(SPEF_TYPICAL)
+	}
+	if { [info exists ::env(CURRENT_SDF)] } {
+		lappend arg_list -sdf_path $::env(CURRENT_SDF)
+	}
+	if { [info exists ::env(CURRENT_SDC)] } {
+		lappend arg_list -sdc_path $::env(CURRENT_SDC)
+	}
+
+	# Add the path if it exists...
+	if { [info exists arg_values(-save_path) ] } {
+		lappend arg_list -save_path $arg_values(-save_path)
+	}
+
+	# Aaand fire!
+	save_views {*}$arg_list
+
+}
+
+proc run_post_run_hooks {} {
+	if { [file exists $::env(DESIGN_DIR)/hooks/post_run.py]} {
+		puts_info "Running post run hook"
+		set result [exec $::env(OPENROAD_BIN) -python $::env(DESIGN_DIR)/hooks/post_run.py]
+		puts_info "$result"
+	} else {
+		puts_info "hooks/post_run.py not found, skipping"
+	}
+}
+
+
+
+
+proc gen_pdn {args} {
+    puts_info "Generating PDN..."
+    TIMER::timer_start
+	
+    set ::env(SAVE_DEF) [index_file $::env(floorplan_tmpfiles).def]
+    set ::env(PGA_RPT_FILE) [index_file $::env(floorplan_tmpfiles).pga.rpt]
+
+    run_openroad_script $::env(SCRIPTS_DIR)/openroad/pdn.tcl \
+        |& -indexed_log [index_file $::env(floorplan_logs)/pdn.log]
+
+
+    TIMER::timer_stop
+    exec echo "[TIMER::get_runtime]" | python3 $::env(SCRIPTS_DIR)/write_runtime.py "pdn generation - openroad"
+
+    quit_on_unconnected_pdn_nodes
+
+    set_def $::env(SAVE_DEF)
+}
+
+proc run_power_grid_generation {args} {
+
+	if {[info exists ::env(FP_PDN_POWER_STRAPS)]} {
+	     set power_domains [split $::env(FP_PDN_POWER_STRAPS) ","]
+	}
+
+	# internal macros power connections 
+	if {[info exists ::env(FP_PDN_MACRO_HOOKS)]} {
+		set macro_hooks [dict create]
+		set pdn_hooks [split $::env(FP_PDN_MACRO_HOOKS) ","]
+		foreach pdn_hook $pdn_hooks {
+			set instance_name [lindex $pdn_hook 0]
+			set power_net [lindex $pdn_hook 1]
+			set ground_net [lindex $pdn_hook 2]
+			dict append macro_hooks $instance_name [subst {$power_net $ground_net}]
+		}
+		
+		set power_net_indx [lsearch $::env(VDD_NETS) $power_net]
+		set ground_net_indx [lsearch $::env(GND_NETS) $ground_net]
+
+		# make sure that the specified power domains exist.
+		if { $power_net_indx == -1  || $ground_net_indx == -1 || $power_net_indx != $ground_net_indx } {
+			puts_err "Can't find $power_net and $ground_net domain. \
+			Make sure that both exist in $::env(VDD_NETS) and $::env(GND_NETS)." 
+		} 
+	}
+	
+	# generate multiple power grids per pair of (VDD,GND)
+	# offseted by WIDTH + SPACING
+	foreach domain $power_domains {
+		set ::env(VDD_NET)       [lindex $domain 0]
+	        set ::env(GND_NET)       [lindex $domain 1]
+	        set ::env(_WITH_STRAPS)  [lindex $domain 2]
+
+	        puts_info "Connecting Power: $::env(VDD_NET) & $::env(GND_NET) to All internal macros."
+		# internal macros power connections
+		set ::env(FP_PDN_MACROS) ""
+		if { $::env(FP_PDN_ENABLE_MACROS_GRID) == 1 } {
+			# if macros connections to power are explicitly set
+			# default behavoir macro pins will be connected to the first power domain
+			if { [info exists ::env(FP_PDN_MACRO_HOOKS)] } {
+				set ::env(FP_PDN_ENABLE_MACROS_GRID) 0
+				foreach {instance_name hooks} $macro_hooks {
+					set power [lindex $hooks 0]
+					set ground [lindex $hooks 1]			 
+					if { $power == $::env(VDD_NET) && $ground == $::env(GND_NET) } {
+						set ::env(FP_PDN_ENABLE_MACROS_GRID) 1
+                                                set ::env(FP_PDN_IRDROP) "1"
+						puts_info "Connecting $instance_name to $power and $ground nets."
+						lappend ::env(FP_PDN_MACROS) $instance_name
+					}
+				}
+			} 
+		} else {
+			puts_warn "All internal macros will not be connected to power $::env(VDD_NET) & $::env(GND_NET)."
+		}
+		
+		gen_pdn
+
+		set ::env(FP_PDN_ENABLE_RAILS) 0
+		set ::env(FP_PDN_ENABLE_MACROS_GRID) 0
+                set ::env(FP_PDN_IRDROP) "0"
+
+		# allow failure until open_pdks is up to date...
+		catch {set ::env(FP_PDN_VOFFSET) [expr $::env(FP_PDN_VOFFSET)+$::env(FP_PDN_VWIDTH)+$::env(FP_PDN_VSPACING)]}
+		catch {set ::env(FP_PDN_HOFFSET) [expr $::env(FP_PDN_HOFFSET)+$::env(FP_PDN_HWIDTH)+$::env(FP_PDN_HSPACING)]}
+
+		catch {set ::env(FP_PDN_CORE_RING_VOFFSET) \
+			[expr $::env(FP_PDN_CORE_RING_VOFFSET)\
+			+2*($::env(FP_PDN_CORE_RING_VWIDTH)\
+			+max($::env(FP_PDN_CORE_RING_VSPACING), $::env(FP_PDN_CORE_RING_HSPACING)))]}
+		catch {set ::env(FP_PDN_CORE_RING_HOFFSET) [expr $::env(FP_PDN_CORE_RING_HOFFSET)\
+			+2*($::env(FP_PDN_CORE_RING_HWIDTH)+\
+			max($::env(FP_PDN_CORE_RING_VSPACING), $::env(FP_PDN_CORE_RING_HSPACING)))]}
+		puts "FP_PDN_VOFFSET: $::env(FP_PDN_VOFFSET)"
+		puts "FP_PDN_HOFFSET: $::env(FP_PDN_HOFFSET)"
+		puts "FP_PDN_CORE_RING_VOFFSET: $::env(FP_PDN_CORE_RING_VOFFSET)"
+		puts "FP_PDN_CORE_RING_HOFFSET: $::env(FP_PDN_CORE_RING_HOFFSET)"
+
+	}
+	set ::env(FP_PDN_ENABLE_RAILS) 1
+}
+
+
+proc run_floorplan {args} {
+	puts_info "Running Floorplanning..."
+	# |----------------------------------------------------|
+	# |----------------   2. FLOORPLAN   ------------------|
+	# |----------------------------------------------------|
+	#
+	# intial fp
+	init_floorplan
+
+	# check for deprecated io variables
+	if { [info exists ::env(FP_IO_HMETAL)]} {
+		set ::env(FP_IO_HLAYER) [lindex $::env(TECH_METAL_LAYERS) [expr {$::env(FP_IO_HMETAL) - 1}]]
+		puts_warn "You're using FP_IO_HMETAL in your configuration, which is a deprecated variable that will be removed in the future."
+		puts_warn "We recommend you update your configuration as follows:"
+		puts_warn "\tset ::env(FP_IO_HLAYER) {$::env(FP_IO_HLAYER)}"
+	}
+
+	if { [info exists ::env(FP_IO_VMETAL)]} {
+		set ::env(FP_IO_VLAYER) [lindex $::env(TECH_METAL_LAYERS) [expr {$::env(FP_IO_VMETAL) - 1}]]
+		puts_warn "You're using FP_IO_VMETAL in your configuration, which is a deprecated variable that will be removed in the future."
+		puts_warn "We recommend you update your configuration as follows:"
+		puts_warn "\tset ::env(FP_IO_VLAYER) {$::env(FP_IO_VLAYER)}"
+	}
+
+
+	# place io
+	if { [info exists ::env(FP_PIN_ORDER_CFG)] } {
+		place_io_ol
+	} else {
+		if { [info exists ::env(FP_CONTEXT_DEF)] && [info exists ::env(FP_CONTEXT_LEF)] } {
+			place_io
+			global_placement_or
+			place_contextualized_io \
+				-lef $::env(FP_CONTEXT_LEF) \
+				-def $::env(FP_CONTEXT_DEF)
+		} else {
+			place_io
+		}
+	}
+
+	apply_def_template
+
+	if { [info exist ::env(EXTRA_LEFS)] } {
+		if { [info exist ::env(MACRO_PLACEMENT_CFG)] } {
+			file copy -force $::env(MACRO_PLACEMENT_CFG) $::env(placement_tmpfiles)/macro_placement.cfg
+			manual_macro_placement f
+		} else {
+			global_placement_or
+			basic_macro_placement
+		}
+	}
+
+	tap_decap_or
+
+	scrot_klayout -layout $::env(CURRENT_DEF) $::env(floorplan_logs)/screenshot.log
+
+	run_power_grid_generation
+}
 
 
 proc run_flow {args} {
-       set script_dir [file dirname [file normalize [info script]]]
-
-		set options {
+	set options {
 		{-design required}
+		{-from optional}
+		{-to optional}
 		{-save_path optional}
-		{-no_lvs optional}
-	    {-no_drc optional}
-	    {-no_antennacheck optional}
+		{-override_env optional}
 	}
-	set flags {-save}
-	parse_key_args "run_flow" args arg_values $options flags_map $flags -no_consume
-
+	set flags {-save -run_hooks -no_lvs -no_drc -no_antennacheck }
+	parse_key_args "run_non_interactive_mode" args arg_values $options flags_map $flags -no_consume
 	prep {*}$args
+    # signal trap SIGINT save_state;
 
-        set LVS_ENABLED 1
-        set DRC_ENABLED 0
-        set ANTENNACHECK_ENABLED 1
+	if { [info exists arg_values(-override_env)] } {
+		set env_overrides [split $arg_values(-override_env) ','] 
+		foreach override $env_overrides {
+			set kva [split $override '=']
+			set key [lindex $kva 0]
+			set value [lindex $kva 1]
+			set ::env(${key}) $value
+		}
+	}
 
-        set steps [dict create "synthesis" {run_synthesis "" } \
-                "floorplan" {run_floorplan ""} \
-                "placement" {run_placement_step ""} \
-                "cts" {run_cts_step ""} \
-                "routing" {run_routing_step ""} \
-                "diode_insertion" {run_diode_insertion_2_5_step ""} \
-                "power_pins_insertion" {run_power_pins_insertion_step ""} \
-                "gds_magic" {run_magic ""} \
-                "gds_drc_klayout" {run_klayout ""} \
-                "gds_xor_klayout" {run_klayout_gds_xor ""} \
-                "lvs" "run_lvs_step $LVS_ENABLED" \
-                "drc" "run_drc_step $DRC_ENABLED" \
-                "antenna_check" "run_antenna_check_step $ANTENNACHECK_ENABLED" \
-                "cvc" {run_lef_cvc}
-        ]
+    set LVS_ENABLED 1
+    set DRC_ENABLED 0
+    set ANTENNACHECK_ENABLED 1
 
-       set_if_unset arg_values(-to) "cvc";
+    set steps [dict create \
+		"synthesis" {run_synthesis "" } \
+		"floorplan" {run_floorplan ""} \
+		"placement" {run_placement_step ""} \
+		"cts" {run_cts_step ""} \
+		"routing" {run_routing_step ""}\
+                "eco" {run_eco_step ""} \
+		"diode_insertion" {run_diode_insertion_2_5_step ""} \
+		"gds_magic" {run_magic ""} \
+		"gds_drc_klayout" {run_klayout ""} \
+		"gds_xor_klayout" {run_klayout_gds_xor ""} \
+		"lvs" "run_lvs_step $LVS_ENABLED" \
+		"drc" "run_drc_step $DRC_ENABLED" \
+		"antenna_check" "run_antenna_check_step $ANTENNACHECK_ENABLED" \
+		"cvc" {run_lef_cvc}
+    ]
 
-       if {  [info exists ::env(CURRENT_STEP) ] } {
-           puts "\[INFO\]:Picking up where last execution left off"
-           puts [format "\[INFO\]:Current stage is %s " $::env(CURRENT_STEP)]
-       } else {
-           set ::env(CURRENT_STEP) "synthesis";
-       }
-       set_if_unset arg_values(-from) $::env(CURRENT_STEP);
-       set exe 0;
-       dict for {step_name step_exe} $steps {
-           if { [ string equal $arg_values(-from) $step_name ] } {
-               set exe 1;
-           }
+    set_if_unset arg_values(-to) "cvc";
 
-           if { $exe } {
-               # For when it fails
-               set ::env(CURRENT_STEP) $step_name
-               [lindex $step_exe 0] [lindex $step_exe 1] ;
-           }
+	if {  [info exists ::env(CURRENT_STEP) ] } {
+        puts "\[INFO\]:Picking up where last execution left off"
+        puts [format "\[INFO\]:Current stage is %s " $::env(CURRENT_STEP)]
+    } else {
+        set ::env(CURRENT_STEP) "synthesis";
+    }
 
-           if { [ string equal $arg_values(-to) $step_name ] } {
-               set exe 0:
-               break;
-           }
+    set_if_unset arg_values(-from) $::env(CURRENT_STEP);
+    set exe 0;
+    dict for {step_name step_exe} $steps {
+        if { [ string equal $arg_values(-from) $step_name ] } {
+            set exe 1;
+        }
 
-       }
+        if { $exe } {
+            # For when it fails
+            set ::env(CURRENT_STEP) $step_name
+            [lindex $step_exe 0] [lindex $step_exe 1] ;
+        }
 
-       # for when it resumes
-       set steps_as_list [dict keys $steps]
-       set next_idx [expr [lsearch $steps_as_list $::env(CURRENT_STEP)] + 1]
-       set ::env(CURRENT_STEP) [lindex $steps_as_list $next_idx]
+        if { [ string equal $arg_values(-to) $step_name ] } {
+            set exe 0:
+            break;
+        }
 
+    }
+
+    # for when it resumes
+    set steps_as_list [dict keys $steps]
+    set next_idx [expr [lsearch $steps_as_list $::env(CURRENT_STEP)] + 1]
+    set ::env(CURRENT_STEP) [lindex $steps_as_list $next_idx]
+
+	# Saves to <RUN_DIR>/results/final
+	if { $::env(SAVE_FINAL_VIEWS) == "1" } {
+		save_final_views
+	}
+
+	# Saves to design directory or custom
 	if {  [info exists flags_map(-save) ] } {
 		if { ! [info exists arg_values(-save_path)] } {
-			set arg_values(-save_path) ""
+			set arg_values(-save_path) $::env(DESIGN_DIR)
 		}
-		save_views 	-lef_path $::env(magic_result_file_tag).lef \
-			-def_path $::env(CURRENT_DEF) \
-			-gds_path $::env(magic_result_file_tag).gds \
-			-mag_path $::env(magic_result_file_tag).mag \
-			-maglef_path $::env(magic_result_file_tag).lef.mag \
-			-spice_path $::env(magic_result_file_tag).spice \
-			-spef_path $::env(CURRENT_SPEF) \
-			-verilog_path $::env(CURRENT_NETLIST) \
-			-save_path $arg_values(-save_path) \
+		save_final_views\
+			-save_path $arg_values(-save_path)\
 			-tag $::env(RUN_TAG)
 	}
-
-
 	calc_total_runtime
 	save_state
 	generate_final_summary_report
 	
 	check_timing_violations
+	
+	if { [info exists arg_values(-save_path)]\
+	    && $arg_values(-save_path) != "" } {
+	    set ::env(HOOK_OUTPUT_PATH) "[file normalize $arg_values(-save_path)]"
+	} else {
+	    set ::env(HOOK_OUTPUT_PATH) $::env(RESULTS_DIR)/final
+	}
+	
+	if {[info exists flags_map(-run_hooks)]} {
+		run_post_run_hooks
+	}
+	
+	puts_success "Flow complete."
 
-	puts_success "Flow Completed Without Fatal Errors."
+	show_warnings "Note that the following warnings have been generated:"
 
 }
 
diff --git a/openlane/user_project_wrapper/macro.cfg b/openlane/user_project_wrapper/macro.cfg
index ac93699..729781f 100644
--- a/openlane/user_project_wrapper/macro.cfg
+++ b/openlane/user_project_wrapper/macro.cfg
@@ -15,4 +15,4 @@
 
 
 u_intercon              1850            700            N
-u_wb_host               1450            075            N
+u_wb_host               1450            100            N
diff --git a/openlane/user_project_wrapper/pdn_cfg.tcl b/openlane/user_project_wrapper/pdn_cfg.tcl
index c23f68f..7813f95 100644
--- a/openlane/user_project_wrapper/pdn_cfg.tcl
+++ b/openlane/user_project_wrapper/pdn_cfg.tcl
@@ -13,10 +13,12 @@
 
 if { [info exists ::env(FP_PDN_ENABLE_GLOBAL_CONNECTIONS)] } {
     if { $::env(FP_PDN_ENABLE_GLOBAL_CONNECTIONS) == 1 } {
-        add_global_connection -net $::env(VDD_NET) -inst_pattern .* -pin_pattern {VPWR} -power
-        add_global_connection -net $::env(VDD_NET) -inst_pattern .* -pin_pattern {VPB} -power
-        add_global_connection -net $::env(GND_NET) -inst_pattern .* -pin_pattern {VGND} -ground
-        add_global_connection -net $::env(GND_NET) -inst_pattern .* -pin_pattern {VNB} -ground
+        foreach power_pin $::env(STD_CELL_POWER_PINS) {
+            add_global_connection -net $::env(VDD_NET) -inst_pattern .* -pin_pattern $power_pin -power
+        }
+        foreach ground_pin $::env(STD_CELL_GROUND_PINS) {
+            add_global_connection -net $::env(GND_NET) -inst_pattern .* -pin_pattern $ground_pin -ground
+        }
     }
 }
 
@@ -27,8 +29,10 @@
 if { $::env(DESIGN_IS_CORE) == 1 } {
     # Used if the design is the core of the chip
     define_pdn_grid -name stdcell_grid -starts_with POWER -voltage_domain CORE -pins [subst {$::env(FP_PDN_LOWER_LAYER) $::env(FP_PDN_UPPER_LAYER)}]
-    add_pdn_stripe -grid stdcell_grid -layer $::env(FP_PDN_LOWER_LAYER) -width $::env(FP_PDN_VWIDTH) -pitch $::env(FP_PDN_VPITCH) -offset $::env(FP_PDN_VOFFSET) -starts_with POWER
-    add_pdn_stripe -grid stdcell_grid -layer $::env(FP_PDN_UPPER_LAYER) -width $::env(FP_PDN_HWIDTH) -pitch $::env(FP_PDN_HPITCH) -offset $::env(FP_PDN_HOFFSET) -starts_with POWER
+    if { $::env(_WITH_STRAPS) } {
+        add_pdn_stripe -grid stdcell_grid -layer $::env(FP_PDN_LOWER_LAYER) -width $::env(FP_PDN_VWIDTH) -pitch $::env(FP_PDN_VPITCH) -offset $::env(FP_PDN_VOFFSET) -starts_with POWER
+        add_pdn_stripe -grid stdcell_grid -layer $::env(FP_PDN_UPPER_LAYER) -width $::env(FP_PDN_HWIDTH) -pitch $::env(FP_PDN_HPITCH) -offset $::env(FP_PDN_HOFFSET) -starts_with POWER
+    } 
     add_pdn_connect -grid stdcell_grid -layers [subst {$::env(FP_PDN_LOWER_LAYER) $::env(FP_PDN_UPPER_LAYER)}]
 } else {
     # Used if the design is a macro in the core
@@ -45,10 +49,10 @@
 
 # Adds the core ring if enabled.
 if { $::env(FP_PDN_CORE_RING) == 1 } {
-    add_pdn_ring -grid stdcell_grid -layer [subst {$::env(FP_PDN_LOWER_LAYER) $::env(FP_PDN_UPPER_LAYER)}] \
-                 -widths [subst {$::env(FP_PDN_CORE_RING_VWIDTH) $::env(FP_PDN_CORE_RING_HWIDTH)}] \
-                 -spacings [subst {$::env(FP_PDN_CORE_RING_VSPACING) $::env(FP_PDN_CORE_RING_HSPACING)}] \
-                 -core_offset [subst {$::env(FP_PDN_CORE_RING_VOFFSET) $::env(FP_PDN_CORE_RING_HOFFSET)}]
+    add_pdn_ring -grid stdcell_grid -layer [subst {$::env(FP_PDN_LOWER_LAYER) $::env(FP_PDN_UPPER_LAYER)}]  \
+                     -widths [subst {$::env(FP_PDN_CORE_RING_VWIDTH) $::env(FP_PDN_CORE_RING_HWIDTH)}] \
+                     -spacings [subst {$::env(FP_PDN_CORE_RING_VSPACING) $::env(FP_PDN_CORE_RING_HSPACING)}] \
+                     -core_offset [subst {$::env(FP_PDN_CORE_RING_VOFFSET) $::env(FP_PDN_CORE_RING_HOFFSET)}]
 }
 
 # A general macro that follows the premise of the set heirarchy. You may want to modify this or add other macro configs
@@ -58,7 +62,7 @@
     orient {R0 R180 MX MY R90 R270 MXR90 MYR90}
     power_pins $::env(VDD_NET)
     ground_pins $::env(GND_NET)
-    blockages "li1 met1 met2 met3 met4"
+    blockages $::env(MACRO_BLOCKAGES_LAYER)
     straps {
     }
     connect {{$::env(FP_PDN_LOWER_LAYER)_PIN_ver $::env(FP_PDN_UPPER_LAYER)}}
@@ -70,18 +74,18 @@
         foreach macro_instance $::env(FP_PDN_MACROS) {
             set macro_instance_grid [subst $macro] 
             dict append $macro_instance_grid instance $macro_instance
-            set ::halo [list $::env(FP_HORIZONTAL_HALO) $::env(FP_VERTICAL_HALO)]
+            set ::halo [list $::env(FP_PDN_HORIZONTAL_HALO) $::env(FP_PDN_VERTICAL_HALO)]
             pdngen::specify_grid macro [subst $macro_instance_grid]
         }
     } else {
-        set ::halo [list $::env(FP_HORIZONTAL_HALO) $::env(FP_VERTICAL_HALO)]
+        set ::halo [list $::env(FP_PDN_HORIZONTAL_HALO) $::env(FP_PDN_VERTICAL_HALO)]
         pdngen::specify_grid macro [subst $macro]
     }
     # CAN NOT ENABLE THE TCL COMMAND BECAUSE THERE IS NO ARGUMENT FOR SPECIFYING THE POWER AND GROUND PIN NAMES ON THE MACRO
-    # define_pdn_grid -macro -orient {R0 R180 MX MY R90 R270 MXR90 MYR90} -grid_over_pg_pins -starts_with POWER -pin_direction vertical -halo [subst {$::env(FP_HORIZONTAL_HALO) $::env(FP_VERTICAL_HALO)}]
+    # define_pdn_grid -macro -orient {R0 R180 MX MY R90 R270 MXR90 MYR90} -grid_over_pg_pins -starts_with POWER -pin_direction vertical -halo [subst {$::env(FP_PDN_HORIZONTAL_HALO) $::env(FP_PDN_VERTICAL_HALO)}]
     # add_pdn_connect -layers [subst {$::env(FP_PDN_LOWER_LAYER) $::env(FP_PDN_UPPER_LAYER)}]
 } else {
-    define_pdn_grid -macro -orient {R0 R180 MX MY R90 R270 MXR90 MYR90} -grid_over_pg_pins -starts_with POWER -halo [subst {$::env(FP_HORIZONTAL_HALO) $::env(FP_VERTICAL_HALO)}]
+    define_pdn_grid -macro -orient {R0 R180 MX MY R90 R270 MXR90 MYR90} -grid_over_pg_pins -starts_with POWER -halo [subst {$::env(FP_PDN_HORIZONTAL_HALO) $::env(FP_PDN_VERTICAL_HALO)}]
 }
 
 # POWER or GROUND #Std. cell rails starting with power or ground rails at the bottom of the core area
diff --git a/openlane/wb_host/config.tcl b/openlane/wb_host/config.tcl
index cb28644..fcd1da4 100755
--- a/openlane/wb_host/config.tcl
+++ b/openlane/wb_host/config.tcl
@@ -75,7 +75,7 @@
 set ::env(FP_PIN_ORDER_CFG) $::env(DESIGN_DIR)/pin_order.cfg
 
 set ::env(FP_SIZING) absolute
-set ::env(DIE_AREA) "0 0 800 250"
+set ::env(DIE_AREA) "0 0 800 200"
 
 
 # If you're going to use multiple power domains, then keep this disabled.
@@ -85,7 +85,7 @@
 
 
 set ::env(PL_TIME_DRIVEN) 1
-set ::env(PL_TARGET_DENSITY) "0.33"
+set ::env(PL_TARGET_DENSITY) "0.35"
 
 
 
diff --git a/openlane/wb_host/pin_order.cfg b/openlane/wb_host/pin_order.cfg
index 4fac1a3..a29f24b 100644
--- a/openlane/wb_host/pin_order.cfg
+++ b/openlane/wb_host/pin_order.cfg
@@ -167,7 +167,7 @@
 
 #E
 
-uartm_rxd           200 0 2
+uartm_rxd           100 0 2
 uartm_txd
 
 
diff --git a/signoff/mbist_wrapper/final_summary_report.csv b/signoff/mbist_wrapper/final_summary_report.csv
index b34cb4b..b6c97f9 100644
--- a/signoff/mbist_wrapper/final_summary_report.csv
+++ b/signoff/mbist_wrapper/final_summary_report.csv
@@ -1,2 +1,2 @@
 ,design,design_name,config,flow_status,total_runtime,routed_runtime,(Cell/mm^2)/Core_Util,DIEAREA_mm^2,CellPer_mm^2,OpenDP_Util,Peak_Memory_Usage_MB,cell_count,tritonRoute_violations,Short_violations,MetSpc_violations,OffGrid_violations,MinHole_violations,Other_violations,Magic_violations,antenna_violations,lvs_total_errors,cvc_total_errors,klayout_violations,wire_length,vias,wns,pl_wns,optimized_wns,fastroute_wns,spef_wns,tns,pl_tns,optimized_tns,fastroute_tns,spef_tns,HPWL,routing_layer1_pct,routing_layer2_pct,routing_layer3_pct,routing_layer4_pct,routing_layer5_pct,routing_layer6_pct,wires_count,wire_bits,public_wires_count,public_wire_bits,memories_count,memory_bits,processes_count,cells_pre_abc,AND,DFF,NAND,NOR,OR,XOR,XNOR,MUX,inputs,outputs,level,EndCaps,TapCells,Diodes,Total_Physical_Cells,suggested_clock_frequency,suggested_clock_period,CLOCK_PERIOD,SYNTH_STRATEGY,SYNTH_MAX_FANOUT,FP_CORE_UTIL,FP_ASPECT_RATIO,FP_PDN_VPITCH,FP_PDN_HPITCH,PL_TARGET_DENSITY,GLB_RT_ADJUSTMENT,STD_CELL_LIBRARY,CELL_PAD,DIODE_INSERTION_STRATEGY
-0,/project/openlane/mbist_wrapper,mbist_wrapper,mbist_wrapper,flow_completed,0h12m21s,-1,22596.825396825396,0.315,11298.412698412698,14.27,692.75,3559,0,0,0,0,0,0,-1,14,0,0,-1,493339,48549,-0.67,-6.16,-1,-1.48,-1,-0.67,-2838.32,-1,-1.64,-1,413236407.0,0.23,48.61,12.47,22.58,0.0,-1,2596,7282,753,5382,0,0,0,2664,0,0,0,0,0,0,0,4,1049,798,17,138,4082,0,4220,90.9090909090909,11,10,AREA 0,4,50,1,140,140,0.3,0.0,sky130_fd_sc_hd,4,4
+0,/project/openlane/mbist_wrapper,mbist_wrapper,mbist_wrapper,flow_completed,0h14m13s,-1,26073.260073260073,0.273,13036.630036630037,16.48,695.21,3559,0,0,0,0,0,0,-1,16,0,0,-1,445806,47865,-0.67,-5.91,-1,-1.78,-1,-0.67,-2737.45,-1,-1.78,-1,357342288.0,5.91,47.85,15.03,24.02,0.01,-1,2596,7282,753,5382,0,0,0,2664,0,0,0,0,0,0,0,4,1049,798,17,138,3514,0,3652,90.9090909090909,11,10,AREA 0,4,50,1,140,140,0.35,0.0,sky130_fd_sc_hd,4,4
diff --git a/signoff/user_project_wrapper/PDK_SOURCES b/signoff/user_project_wrapper/PDK_SOURCES
index ca3684a..22e7dc1 100644
--- a/signoff/user_project_wrapper/PDK_SOURCES
+++ b/signoff/user_project_wrapper/PDK_SOURCES
@@ -1,6 +1,3 @@
--ne openlane 
-8d686c081c2c9aefa16dbbd8ccf5bc8f4dcabc4b
--ne skywater-pdk 
-c094b6e83a4f9298e47f696ec5a7fd53535ec5eb
--ne open_pdks 
-14db32aa8ba330e88632ff3ad2ff52f4f4dae1ad
+openlane 70923d7fbd8998c8da87d905cf9e69bffc13709f
+skywater-pdk c094b6e83a4f9298e47f696ec5a7fd53535ec5eb
+open_pdks 476f7428f7f686de51a5164c702629a9b9f2da46
diff --git a/signoff/user_project_wrapper/final_summary_report.csv b/signoff/user_project_wrapper/final_summary_report.csv
index ffcbae2..ad0b712 100644
--- a/signoff/user_project_wrapper/final_summary_report.csv
+++ b/signoff/user_project_wrapper/final_summary_report.csv
@@ -1,2 +1,2 @@
 ,design,design_name,config,flow_status,total_runtime,routed_runtime,(Cell/mm^2)/Core_Util,DIEAREA_mm^2,CellPer_mm^2,OpenDP_Util,Peak_Memory_Usage_MB,cell_count,tritonRoute_violations,Short_violations,MetSpc_violations,OffGrid_violations,MinHole_violations,Other_violations,Magic_violations,antenna_violations,lvs_total_errors,cvc_total_errors,klayout_violations,wire_length,vias,wns,pl_wns,optimized_wns,fastroute_wns,spef_wns,tns,pl_tns,optimized_tns,fastroute_tns,spef_tns,HPWL,routing_layer1_pct,routing_layer2_pct,routing_layer3_pct,routing_layer4_pct,routing_layer5_pct,routing_layer6_pct,wires_count,wire_bits,public_wires_count,public_wire_bits,memories_count,memory_bits,processes_count,cells_pre_abc,AND,DFF,NAND,NOR,OR,XOR,XNOR,MUX,inputs,outputs,level,EndCaps,TapCells,Diodes,Total_Physical_Cells,suggested_clock_frequency,suggested_clock_period,CLOCK_PERIOD,SYNTH_STRATEGY,SYNTH_MAX_FANOUT,FP_CORE_UTIL,FP_ASPECT_RATIO,FP_PDN_VPITCH,FP_PDN_HPITCH,PL_TARGET_DENSITY,GLB_RT_ADJUSTMENT,STD_CELL_LIBRARY,CELL_PAD,DIODE_INSERTION_STRATEGY
-0,/project/openlane/user_project_wrapper,user_project_wrapper,user_project_wrapper,flow_completed,0h50m22s,-1,2.724159402241594,10.2784,1.362079701120797,-1,536.41,14,0,0,0,0,0,0,-1,0,0,-1,-1,1382277,9457,0.0,-1,-1,0.0,-1,0.0,-1,-1,0.0,-1,-1,64380.99,4.47,4.45,0.78,0.67,-1,313,2877,313,2877,0,0,0,14,0,0,0,0,0,0,0,0,-1,-1,-1,0,0,0,0,90.9090909090909,11,10,AREA 0,5,50,1,100,90,0.55,0.0,sky130_fd_sc_hd,4,0
+0,/project/openlane/user_project_wrapper,user_project_wrapper,user_project_wrapper,flow completed,0h47m6s0ms,0h3m47s0ms,-2.0,-1,-1,-1,551.22,14,0,0,0,0,0,0,-1,0,0,-1,-1,1417832,9034,0.0,-1,-1,0.0,0.0,0.0,-1,-1,0.0,0.0,-1,0.0,6.57,6.6,1.03,1.28,-1,313,2877,313,2877,0,0,0,14,0,0,0,0,0,0,0,0,-1,-1,-1,0,0,0,0,100.0,10.0,10,AREA 0,5,50,1,80,120,0.55,0.3,sky130_fd_sc_hd,4,0
diff --git a/signoff/wb_host/final_summary_report.csv b/signoff/wb_host/final_summary_report.csv
index 8afeccf..7be1787 100644
--- a/signoff/wb_host/final_summary_report.csv
+++ b/signoff/wb_host/final_summary_report.csv
@@ -1,2 +1,2 @@
 ,design,design_name,config,flow_status,total_runtime,routed_runtime,(Cell/mm^2)/Core_Util,DIEAREA_mm^2,CellPer_mm^2,OpenDP_Util,Peak_Memory_Usage_MB,cell_count,tritonRoute_violations,Short_violations,MetSpc_violations,OffGrid_violations,MinHole_violations,Other_violations,Magic_violations,antenna_violations,lvs_total_errors,cvc_total_errors,klayout_violations,wire_length,vias,wns,pl_wns,optimized_wns,fastroute_wns,spef_wns,tns,pl_tns,optimized_tns,fastroute_tns,spef_tns,HPWL,routing_layer1_pct,routing_layer2_pct,routing_layer3_pct,routing_layer4_pct,routing_layer5_pct,routing_layer6_pct,wires_count,wire_bits,public_wires_count,public_wire_bits,memories_count,memory_bits,processes_count,cells_pre_abc,AND,DFF,NAND,NOR,OR,XOR,XNOR,MUX,inputs,outputs,level,EndCaps,TapCells,Diodes,Total_Physical_Cells,suggested_clock_frequency,suggested_clock_period,CLOCK_PERIOD,SYNTH_STRATEGY,SYNTH_MAX_FANOUT,FP_CORE_UTIL,FP_ASPECT_RATIO,FP_PDN_VPITCH,FP_PDN_HPITCH,PL_TARGET_DENSITY,GLB_RT_ADJUSTMENT,STD_CELL_LIBRARY,CELL_PAD,DIODE_INSERTION_STRATEGY
-0,/project/openlane/wb_host,wb_host,wb_host,flow_completed,0h9m5s,-1,41570.0,0.2,20785.0,27.16,665.55,4157,0,0,0,0,0,0,0,7,0,0,-1,342762,47421,0.0,-0.39,-1,-0.15,-1,0.0,-47.54,-1,-0.52,-1,279734014.0,2.28,55.55,17.8,16.11,0.01,-1,3490,6163,1024,3553,0,0,0,3793,0,0,0,0,0,0,0,4,1233,1205,17,166,2592,0,2758,90.9090909090909,11,10,AREA 0,4,50,1,100,100,0.33,0.0,sky130_fd_sc_hd,4,4
+0,/project/openlane/wb_host,wb_host,wb_host,flow_completed,0h10m23s,-1,51962.5,0.16,25981.25,34.69,645.3,4157,0,0,0,0,0,0,0,5,0,0,-1,329531,47752,0.0,-0.23,-1,0.0,-1,0.0,-26.36,-1,0.0,-1,265017619.0,4.63,61.49,19.26,29.21,0.09,-1,3490,6163,1024,3553,0,0,0,3793,0,0,0,0,0,0,0,4,1233,1205,17,130,2043,0,2173,90.9090909090909,11,10,AREA 0,4,50,1,100,100,0.35,0.0,sky130_fd_sc_hd,4,4
diff --git a/signoff/yifive/final_summary_report.csv b/signoff/yifive/final_summary_report.csv
index cedb7e9..09acd87 100644
--- a/signoff/yifive/final_summary_report.csv
+++ b/signoff/yifive/final_summary_report.csv
@@ -1,2 +1,2 @@
 ,design,design_name,config,flow_status,total_runtime,routed_runtime,(Cell/mm^2)/Core_Util,DIEAREA_mm^2,CellPer_mm^2,OpenDP_Util,Peak_Memory_Usage_MB,cell_count,tritonRoute_violations,Short_violations,MetSpc_violations,OffGrid_violations,MinHole_violations,Other_violations,Magic_violations,antenna_violations,lvs_total_errors,cvc_total_errors,klayout_violations,wire_length,vias,wns,pl_wns,optimized_wns,fastroute_wns,spef_wns,tns,pl_tns,optimized_tns,fastroute_tns,spef_tns,HPWL,routing_layer1_pct,routing_layer2_pct,routing_layer3_pct,routing_layer4_pct,routing_layer5_pct,routing_layer6_pct,wires_count,wire_bits,public_wires_count,public_wire_bits,memories_count,memory_bits,processes_count,cells_pre_abc,AND,DFF,NAND,NOR,OR,XOR,XNOR,MUX,inputs,outputs,level,EndCaps,TapCells,Diodes,Total_Physical_Cells,suggested_clock_frequency,suggested_clock_period,CLOCK_PERIOD,SYNTH_STRATEGY,SYNTH_MAX_FANOUT,FP_CORE_UTIL,FP_ASPECT_RATIO,FP_PDN_VPITCH,FP_PDN_HPITCH,PL_TARGET_DENSITY,GLB_RT_ADJUSTMENT,STD_CELL_LIBRARY,CELL_PAD,DIODE_INSERTION_STRATEGY
-0,/project/openlane/yifive,ycr1_top_wb,yifive,flow_completed,1h10m32s,-1,62420.32667876588,1.033125,31210.16333938294,35.63,1498.7,32244,0,-1,-1,-1,-1,0,-1,1,0,-1,-1,2409836,367161,-15.33,-49.98,-1,-0.01,-1,-31946.93,-11474.41,-1,-0.01,-1,1780383901.0,5.46,44.53,58.67,5.38,10.76,0.0,26922,45935,1722,20366,0,0,0,32127,0,0,0,0,0,0,0,4,7920,8456,48,1030,14217,0,15247,90.9090909090909,11,10,AREA 0,4,50,1,100,100,0.36,0.0,sky130_fd_sc_hd,4,4
+0,/project/openlane/yifive,ycr1_top_wb,yifive,flow_completed,1h17m16s,-1,62420.32667876588,1.033125,31210.16333938294,35.63,1498.63,32244,0,-1,-1,-1,-1,0,-1,1,0,-1,-1,2409836,367161,-15.33,-49.98,-1,-0.01,-1,-31946.93,-11474.41,-1,-0.01,-1,1780383901.0,5.46,44.53,58.67,5.38,10.76,0.0,26922,45935,1722,20366,0,0,0,32127,0,0,0,0,0,0,0,4,7920,8456,48,1030,14217,0,15247,90.9090909090909,11,10,AREA 0,4,50,1,100,100,0.36,0.0,sky130_fd_sc_hd,4,4
diff --git a/verilog/rtl/qspim b/verilog/rtl/qspim
new file mode 160000
index 0000000..644fc5e
--- /dev/null
+++ b/verilog/rtl/qspim
@@ -0,0 +1 @@
+Subproject commit 644fc5e86bf08279ed257519456199e85d9584f9
diff --git a/verilog/rtl/user_project_wrapper.v b/verilog/rtl/user_project_wrapper.v
index 865063a..cb381a3 100644
--- a/verilog/rtl/user_project_wrapper.v
+++ b/verilog/rtl/user_project_wrapper.v
@@ -217,7 +217,7 @@
     // Note that analog I/O is not available on the 7 lowest-numbered
     // GPIO pads, and so the analog_io indexing is offset from the
     // GPIO indexing by 7 (also upper 2 GPIOs do not have analog_io).
-    inout [`MPRJ_IO_PADS-10:0] analog_io,
+    inout [28:0] analog_io,
  
     // Logic Analyzer Signals
     input  wire [127:0]                la_data_in      ,
diff --git a/verilog/rtl/yifive/ycr1c b/verilog/rtl/yifive/ycr1c
new file mode 160000
index 0000000..7727aeb
--- /dev/null
+++ b/verilog/rtl/yifive/ycr1c
@@ -0,0 +1 @@
+Subproject commit 7727aeba9e18475aff727af00effb72ad6930969