| #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 |
| } |
| } |