Merge pull request #2352 from KLayout/bugfix/issue-2345

A contribution to issue #2345 mitigation
This commit is contained in:
Matthias Köfferlein 2026-05-20 19:06:12 +02:00 committed by GitHub
commit 10860d5a68
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 443 additions and 16 deletions

View File

@ -103,6 +103,13 @@ EdgePairs::EdgePairs (DeepShapeStore &dss)
mp_delegate = new DeepEdgePairs (DeepLayer (&dss, layout_index, dss.layout (layout_index).insert_layer ())); mp_delegate = new DeepEdgePairs (DeepLayer (&dss, layout_index, dss.layout (layout_index).insert_layer ()));
} }
void
EdgePairs::convert_to_deep (const db::DeepLayer &layer)
{
tl_assert (mp_delegate->deep () == 0);
set_delegate (copy_data_id (new db::DeepEdgePairs (layer)));
}
void void
EdgePairs::write (const std::string &fn) const EdgePairs::write (const std::string &fn) const
{ {

View File

@ -204,6 +204,11 @@ public:
*/ */
explicit EdgePairs (DeepShapeStore &dss); explicit EdgePairs (DeepShapeStore &dss);
/**
* @brief Converts the shape collection to a deep one using the specified layer
*/
virtual void convert_to_deep (const db::DeepLayer &layer);
/** /**
* @brief Writes the edge pair collection to a file * @brief Writes the edge pair collection to a file
* *

View File

@ -114,6 +114,13 @@ Edges::Edges (DeepShapeStore &dss)
mp_delegate = new DeepEdges (DeepLayer (&dss, layout_index, dss.layout (layout_index).insert_layer ())); mp_delegate = new DeepEdges (DeepLayer (&dss, layout_index, dss.layout (layout_index).insert_layer ()));
} }
void
Edges::convert_to_deep (const db::DeepLayer &layer)
{
tl_assert (mp_delegate->deep () == 0);
set_delegate (copy_data_id (new db::DeepEdges (layer)));
}
const db::RecursiveShapeIterator & const db::RecursiveShapeIterator &
Edges::iter () const Edges::iter () const
{ {

View File

@ -264,6 +264,11 @@ public:
*/ */
explicit Edges (DeepShapeStore &dss); explicit Edges (DeepShapeStore &dss);
/**
* @brief Converts the shape collection to a deep one using the specified layer
*/
virtual void convert_to_deep (const db::DeepLayer &layer);
/** /**
* @brief Implementation of the ShapeCollection interface * @brief Implementation of the ShapeCollection interface
*/ */

View File

@ -130,8 +130,10 @@ void NetlistDeviceExtractor::extract (db::DeepShapeStore &dss, unsigned int layo
std::pair<bool, db::DeepLayer> alias = dss.layer_for_flat (tl::id_of (l->second->get_delegate ())); std::pair<bool, db::DeepLayer> alias = dss.layer_for_flat (tl::id_of (l->second->get_delegate ()));
if (alias.first) { if (alias.first) {
// use deep layer alias for a given flat one (if found) // use deep layer alias for a given flat one (if found) and convert layer to a deep one
layers.push_back (alias.second.layer ()); db::DeepLayer dl = alias.second;
l->second->convert_to_deep (dl);
layers.push_back (dl.layer ());
} else { } else {
throw tl::Exception (tl::sprintf (tl::to_string (tr ("Invalid region passed to input layer '%s' for device extraction (device %s): must be of deep region kind")), ld->name, name ())); throw tl::Exception (tl::sprintf (tl::to_string (tr ("Invalid region passed to input layer '%s' for device extraction (device %s): must be of deep region kind")), ld->name, name ()));
} }

View File

@ -132,6 +132,13 @@ Region::Region (DeepShapeStore &dss)
mp_delegate = new db::DeepRegion (db::DeepLayer (&dss, layout_index, dss.layout (layout_index).insert_layer ())); mp_delegate = new db::DeepRegion (db::DeepLayer (&dss, layout_index, dss.layout (layout_index).insert_layer ()));
} }
void
Region::convert_to_deep (const db::DeepLayer &layer)
{
tl_assert (mp_delegate->deep () == 0);
set_delegate (copy_data_id (new db::DeepRegion (layer)));
}
void void
Region::write (const std::string &fn) const Region::write (const std::string &fn) const
{ {

View File

@ -256,6 +256,11 @@ public:
*/ */
void write (const std::string &fn) const; void write (const std::string &fn) const;
/**
* @brief Converts the shape collection to a deep one using the specified layer
*/
virtual void convert_to_deep (const db::DeepLayer &layer);
/** /**
* @brief Implementation of the ShapeCollection interface * @brief Implementation of the ShapeCollection interface
*/ */

View File

@ -75,7 +75,12 @@ class DB_PUBLIC ShapeCollectionDelegateBase
: public tl::UniqueId : public tl::UniqueId
{ {
public: public:
ShapeCollectionDelegateBase () { } ShapeCollectionDelegateBase ()
: m_data_id (tl::id_of (this))
{
// .. nothing yet ..
}
virtual ~ShapeCollectionDelegateBase () { } virtual ~ShapeCollectionDelegateBase () { }
virtual DeepShapeCollectionDelegateBase *deep () { return 0; } virtual DeepShapeCollectionDelegateBase *deep () { return 0; }
@ -88,6 +93,22 @@ public:
apply_property_translator (db::PropertiesTranslator::make_remove_all ()); apply_property_translator (db::PropertiesTranslator::make_remove_all ());
} }
} }
tl::id_type data_id () const
{
return m_data_id;
}
private:
friend class ShapeCollection;
tl::id_type m_data_id;
// used for conversion to deep
void set_data_id (tl::id_type data_id)
{
m_data_id = data_id;
}
}; };
/** /**
@ -102,6 +123,11 @@ public:
virtual ShapeCollectionDelegateBase *get_delegate () const = 0; virtual ShapeCollectionDelegateBase *get_delegate () const = 0;
/**
* @brief Converts the shape collection to a deep one using the specified layer
*/
virtual void convert_to_deep (const db::DeepLayer &layer) = 0;
/** /**
* @brief Applies a PropertyTranslator * @brief Applies a PropertyTranslator
* *
@ -111,6 +137,14 @@ public:
* delivered by "properties_repository". * delivered by "properties_repository".
*/ */
void apply_property_translator (const db::PropertiesTranslator &pt); void apply_property_translator (const db::PropertiesTranslator &pt);
protected:
template <class Delegate>
Delegate *copy_data_id (Delegate *dlg)
{
dlg->set_data_id (get_delegate ()->data_id ());
return dlg;
}
}; };
} }

View File

@ -99,6 +99,13 @@ Texts::Texts (DeepShapeStore &dss)
mp_delegate = new DeepTexts (DeepLayer (&dss, layout_index, dss.layout (layout_index).insert_layer ())); mp_delegate = new DeepTexts (DeepLayer (&dss, layout_index, dss.layout (layout_index).insert_layer ()));
} }
void
Texts::convert_to_deep (const db::DeepLayer &layer)
{
tl_assert (mp_delegate->deep () == 0);
set_delegate (copy_data_id (new db::DeepTexts (layer)));
}
void void
Texts::write (const std::string &fn) const Texts::write (const std::string &fn) const
{ {

View File

@ -208,6 +208,11 @@ public:
*/ */
void write (const std::string &fn) const; void write (const std::string &fn) const;
/**
* @brief Converts the shape collection to a deep one using the specified layer
*/
virtual void convert_to_deep (const db::DeepLayer &layer);
/** /**
* @brief Implementation of the ShapeCollection interface * @brief Implementation of the ShapeCollection interface
*/ */

View File

@ -630,9 +630,9 @@ static bool is_deep (const db::EdgePairs *ep)
return dynamic_cast<const db::DeepEdgePairs *> (ep->delegate ()) != 0; return dynamic_cast<const db::DeepEdgePairs *> (ep->delegate ()) != 0;
} }
static size_t id (const db::EdgePairs *ep) static size_t data_id (const db::EdgePairs *ep)
{ {
return tl::id_of (ep->delegate ()); return ep->delegate ()->data_id ();
} }
static db::EdgePairs filtered (const db::EdgePairs *r, const gsi::EdgePairFilterBase *f) static db::EdgePairs filtered (const db::EdgePairs *r, const gsi::EdgePairFilterBase *f)
@ -1114,7 +1114,7 @@ Class<db::EdgePairs> decl_EdgePairs (decl_dbShapeCollection, "db", "EdgePairs",
"\n" "\n"
"This method has been added in version 0.26." "This method has been added in version 0.26."
) + ) +
method_ext ("data_id", &id, method_ext ("data_id", &data_id,
"@brief Returns the data ID (a unique identifier for the underlying data storage)\n" "@brief Returns the data ID (a unique identifier for the underlying data storage)\n"
"\n" "\n"
"This method has been added in version 0.26." "This method has been added in version 0.26."

View File

@ -892,9 +892,9 @@ static db::Edges *new_texts_as_dots2 (const db::RecursiveShapeIterator &si, db::
return new db::Edges (db::Region (si).texts_as_dots (pat, pattern, dss)); return new db::Edges (db::Region (si).texts_as_dots (pat, pattern, dss));
} }
static size_t id (const db::Edges *e) static size_t data_id (const db::Edges *e)
{ {
return tl::id_of (e->delegate ()); return e->delegate ()->data_id ();
} }
static std::vector<db::Edges> andnot_with_edges (const db::Edges *r, const db::Edges &other) static std::vector<db::Edges> andnot_with_edges (const db::Edges *r, const db::Edges &other)
@ -2572,7 +2572,7 @@ Class<db::Edges> decl_Edges (decl_dbShapeCollection, "db", "Edges",
"\n" "\n"
"This method has been added in version 0.26." "This method has been added in version 0.26."
) + ) +
method_ext ("data_id", &id, method_ext ("data_id", &data_id,
"@brief Returns the data ID (a unique identifier for the underlying data storage)\n" "@brief Returns the data ID (a unique identifier for the underlying data storage)\n"
"\n" "\n"
"This method has been added in version 0.26." "This method has been added in version 0.26."

View File

@ -1288,9 +1288,9 @@ static bool is_deep (const db::Region *region)
return dynamic_cast<const db::DeepRegion *> (region->delegate ()) != 0; return dynamic_cast<const db::DeepRegion *> (region->delegate ()) != 0;
} }
static size_t id (const db::Region *r) static size_t data_id (const db::Region *r)
{ {
return tl::id_of (r->delegate ()); return r->delegate ()->data_id ();
} }
@ -4214,7 +4214,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"\n" "\n"
"This method has been added in version 0.26." "This method has been added in version 0.26."
) + ) +
method_ext ("data_id", &id, method_ext ("data_id", &data_id,
"@brief Returns the data ID (a unique identifier for the underlying data storage)\n" "@brief Returns the data ID (a unique identifier for the underlying data storage)\n"
"\n" "\n"
"This method has been added in version 0.26." "This method has been added in version 0.26."

View File

@ -477,9 +477,9 @@ static bool is_deep (const db::Texts *t)
return dynamic_cast<const db::DeepTexts *> (t->delegate ()) != 0; return dynamic_cast<const db::DeepTexts *> (t->delegate ()) != 0;
} }
static size_t id (const db::Texts *t) static size_t data_id (const db::Texts *t)
{ {
return tl::id_of (t->delegate ()); return t->delegate ()->data_id ();
} }
static db::Texts filtered (const db::Texts *r, const gsi::TextFilterBase *f) static db::Texts filtered (const db::Texts *r, const gsi::TextFilterBase *f)
@ -686,7 +686,7 @@ Class<db::Texts> decl_Texts (decl_dbShapeCollection, "db", "Texts",
method_ext ("is_deep?", &is_deep, method_ext ("is_deep?", &is_deep,
"@brief Returns true if the edge pair collection is a deep (hierarchical) one\n" "@brief Returns true if the edge pair collection is a deep (hierarchical) one\n"
) + ) +
method_ext ("data_id", &id, method_ext ("data_id", &data_id,
"@brief Returns the data ID (a unique identifier for the underlying data storage)\n" "@brief Returns the data ID (a unique identifier for the underlying data storage)\n"
) + ) +
method ("+|join", &db::Texts::operator+, gsi::arg ("other"), method ("+|join", &db::Texts::operator+, gsi::arg ("other"),

View File

@ -323,7 +323,9 @@ module DRC
# #
# If layers are not named, they will be given a name made from the # If layers are not named, they will be given a name made from the
# \name_prefix and an incremental number when the layer is used for the # \name_prefix and an incremental number when the layer is used for the
# first time. # first time. As the default name prefix is "l", you may can name conflicts
# with auto-named layers, if you register explicit layer names like "l5".
# Consider changing the name prefix in that case to some prefix you are not using.
# #
# \name can only be used once on a layer and the layer names must be # \name can only be used once on a layer and the layer names must be
# unique (not taken by another layer). # unique (not taken by another layer).

View File

@ -362,3 +362,9 @@ TEST(63_FlagMissingPorts)
run_test (_this, "flag_missing_ports", "flag_missing_ports.gds", false, true, "TOP"); run_test (_this, "flag_missing_ports", "flag_missing_ports.gds", false, true, "TOP");
} }
// Split substrate - marker and global connection (issue #2345)
TEST(64_SplitSubstrate)
{
run_test (_this, "split_substrate", "split_substrate.gds", true, false /*no LVS*/);
}

Binary file not shown.

13
testdata/lvs/split_substrate.cir vendored Normal file
View File

@ -0,0 +1,13 @@
* Extracted by KLayout
.SUBCKT TOP A Q IOSUB SUBSTRATE
X$1 \$7 \$1 Q IOSUB IOSUB INV
X$2 \$7 A \$1 SUBSTRATE SUBSTRATE INV
.ENDS TOP
.SUBCKT INV \$2 \$4 \$5 \$1 \$I11
M$1 \$2 \$4 \$5 \$2 PMOS L=0.25U W=0.95U AS=0.73625P AD=0.73625P PS=3.45U
+ PD=3.45U
M$2 \$1 \$4 \$5 \$I11 NMOS L=0.25U W=0.95U AS=0.73625P AD=0.73625P PS=3.45U
+ PD=3.45U
.ENDS INV

BIN
testdata/lvs/split_substrate.gds vendored Normal file

Binary file not shown.

215
testdata/lvs/split_substrate.l2n vendored Normal file
View File

@ -0,0 +1,215 @@
#%l2n-klayout
W(TOP)
U(0.001)
L(l3 '1/0')
L(l4 '3/0')
L(l15 '3/1')
L(l8 '4/0')
L(l11 '5/0')
L(l12 '6/0')
L(l16 '6/1')
L(l13 '7/0')
L(l14 '8/0')
L(l17)
L(l22 '10/0')
L(l7)
L(l10)
L(l2)
L(l9)
L(l6)
L(l21)
L(l18)
L(l20)
L(l19)
C(l3 l3 l10)
C(l4 l4 l15 l11)
C(l15 l4 l15)
C(l8 l8 l12 l10 l2 l9 l6)
CS(l8 l10 l2 l9 l6)
C(l11 l4 l11 l12)
CS(l11 l4)
C(l12 l8 l11 l12 l16 l13)
C(l16 l12 l16)
C(l13 l12 l13 l14)
C(l14 l13 l14 l17)
C(l17 l14 l17)
C(l22 l22 l21 l20)
C(l7 l7 l18 l20)
C(l10 l3 l8 l10)
CS(l10 l3)
C(l2 l8 l2)
C(l9 l8 l9 l21 l19)
C(l6 l8 l6)
C(l21 l22 l9 l21)
CS(l21 l22)
C(l18 l7 l18)
C(l20 l22 l7 l20)
C(l19 l9 l19)
G(l22 IOSUB)
G(l18 SUBSTRATE)
G(l19 SUBSTRATE)
GS(l19 SUBSTRATE)
H(W B('Net with incomplete wiring (soft-connected partial nets)') C(TOP) X('soft-connection-check'))
H(B('\tPartial net #1: TOP/INV[r0 3,1.6]:$1 - $2') C(TOP) Q('(1.5,3.95;1.5,4.85;5.3,4.85;5.3,3.95)'))
H(B('\tPartial net #2: TOP/INV[r0 7.7,1.6]:$2 - $2') C(TOP) Q('(6.2,3.95;6.2,4.85;10,4.85;10,3.95)'))
K(PMOS MOS4)
K(NMOS MOS4)
D(D$PMOS PMOS
T(S
R(l2 (-900 -475) (775 950))
)
T(G
R(l4 (-125 -475) (250 950))
)
T(D
R(l2 (125 -475) (775 950))
)
T(B
R(l3 (-125 -475) (250 950))
)
)
D(D$NMOS NMOS
T(S
R(l6 (-900 -475) (775 950))
)
T(G
R(l4 (-125 -475) (250 950))
)
T(D
R(l6 (125 -475) (775 950))
)
T(B
R(l7 (-125 -475) (250 950))
)
)
X(INV
R((-1500 -800) (3800 4600))
N(1 I($1)
R(l8 (1700 100) (200 200))
R(l8 (-200 -600) (200 200))
R(l8 (-1610 -210) (220 220))
R(l8 (-220 180) (220 220))
R(l12 (890 -760) (800 900))
R(l12 (-1980 -830) (360 760))
R(l13 (-305 -705) (250 250))
R(l13 (-250 150) (250 250))
R(l13 (1175 -225) (200 200))
R(l13 (-200 -600) (200 200))
R(l14 (-3400 -350) (3000 900))
R(l14 (-200 -900) (1000 900))
R(l9 (-700 -950) (400 1000))
R(l6 (-1875 -975) (775 950))
)
N(2 I($2)
R(l3 (-1500 1800) (3000 2000))
R(l3 (-200 -2000) (1000 2000))
R(l8 (-2010 -1310) (220 220))
R(l8 (-220 180) (220 220))
R(l8 (1190 -210) (200 200))
R(l8 (-200 -600) (200 200))
R(l12 (-1680 -280) (360 760))
R(l12 (820 -830) (800 900))
R(l13 (-1925 -775) (250 250))
R(l13 (-250 150) (250 250))
R(l13 (1175 -225) (200 200))
R(l13 (-200 -600) (200 200))
R(l14 (-3400 -350) (3000 900))
R(l14 (-200 -900) (1000 900))
R(l10 (-700 -950) (400 1000))
R(l2 (-1875 -975) (775 950))
)
N(3 I($4)
R(l4 (-125 -250) (250 2500))
R(l4 (-250 -3050) (250 1600))
R(l4 (-250 1200) (250 1600))
)
N(4 I($5)
R(l8 (-510 -310) (220 220))
R(l8 (-220 180) (220 220))
R(l8 (-220 2180) (220 220))
R(l8 (-220 180) (220 220))
R(l12 (-290 -3530) (360 2840))
R(l12 (-360 -2800) (360 760))
R(l12 (-360 2040) (360 760))
R(l2 (-680 -855) (775 950))
R(l6 (-775 -3750) (775 950))
)
N(5 I($I11))
P(2)
P(3)
P(4)
P(1)
P(5)
D(1 D$PMOS
Y(0 2800)
E(L 0.25)
E(W 0.95)
E(AS 0.73625)
E(AD 0.73625)
E(PS 3.45)
E(PD 3.45)
T(S 4)
T(G 3)
T(D 2)
T(B 2)
)
D(2 D$NMOS
Y(0 0)
E(L 0.25)
E(W 0.95)
E(AS 0.73625)
E(AD 0.73625)
E(PS 3.45)
E(PD 3.45)
T(S 4)
T(G 3)
T(D 1)
T(B 5)
)
)
X(TOP
R((1334 621) (9246 5073))
N(1 I($1)
R(l4 (2920 2600) (3980 400))
R(l11 (-300 -300) (200 200))
R(l12 (-300 -300) (690 400))
)
N(2 I(A)
R(l4 (7700 2600) (2880 400))
R(l15 (-2380 -200) (0 0))
)
N(3 I(Q)
R(l12 (1810 2600) (690 400))
R(l16 (-400 -200) (0 0))
)
N(4 I(IOSUB)
R(l22 (1334 621) (4230 5073))
R(l21 (-964 -4594) (400 1000))
R(l20 (-2125 -975) (250 950))
)
N(5 I($7)
R(l3 (4000 3400) (2700 2000))
)
N(6 I(SUBSTRATE)
R(l18 (7575 1125) (250 950))
R(l19 (1475 -975) (400 1000))
)
P(2 I(A))
P(3 I(Q))
P(4 I(IOSUB))
P(6 I(SUBSTRATE))
X(1 INV Y(3000 1600)
P(0 5)
P(1 1)
P(2 3)
P(3 4)
P(4 4)
)
X(2 INV Y(7700 1600)
P(0 5)
P(1 2)
P(2 1)
P(3 6)
P(4 6)
)
)

107
testdata/lvs/split_substrate.lvs vendored Normal file
View File

@ -0,0 +1,107 @@
$lvs_test_source && source($lvs_test_source)
if $lvs_test_target_l2n
report_netlist($lvs_test_target_l2n)
else
report_netlist
end
writer = write_spice(true, false)
$lvs_test_target_cir && target_netlist($lvs_test_target_cir, writer, "Extracted by KLayout")
deep
# Drawing layers
nwell = input(1, 0)
active = input(2, 0)
nplus = input(2, 1)
pplus = input(2, 2)
poly = input(3, 0)
poly_lbl = input(3, 1)
diff_cont = input(4, 0)
poly_cont = input(5, 0)
metal1 = input(6, 0)
metal1_lbl = input(6, 1)
via1 = input(7, 0)
metal2 = input(8, 0)
metal2_lbl = input(8, 1)
iosub = input(10, 0)
# Bulk layer for terminal provisioning
bulk = polygon_layer
psd = nil
nsd = nil
# Computed layers
active_in_nwell = active & nwell
pactive = active_in_nwell & pplus
ntie = active_in_nwell & nplus
pgate = pactive & poly
psd = pactive - pgate
active_outside_nwell = active - nwell
nactive = active_outside_nwell & nplus
ptie = active_outside_nwell & pplus
ngate = nactive & poly
nsd = nactive - ngate
# Device extraction
# PMOS transistor device extraction
extract_devices(mos4("PMOS"), { "SD" => psd, "G" => pgate, "W" => nwell,
"tS" => psd, "tD" => psd, "tG" => poly })
# NMOS transistor device extraction
extract_devices(mos4("NMOS"), { "SD" => nsd, "G" => ngate, "W" => bulk,
"tS" => nsd, "tD" => nsd, "tG" => poly })
# Define connectivity for netlist extraction
# Inter-layer
soft_connect(diff_cont, psd)
soft_connect(diff_cont, nsd)
soft_connect(diff_cont, ptie)
soft_connect(diff_cont, ntie)
soft_connect(ntie, nwell)
soft_connect(poly_cont, poly)
connect(diff_cont, metal1)
connect(poly_cont, metal1)
connect(metal1, via1)
connect(via1, metal2)
# attach labels
connect(poly, poly_lbl)
connect(metal1, metal1_lbl)
connect(metal2, metal2_lbl)
# Split bulk and ptie into inside and outside iosub
(bulk_io, bulk_reg) = bulk.andnot(iosub)
(ptie_io, ptie_reg) = ptie.andnot(iosub)
# connect outside to "SUBSTRATE" globally
connect(bulk, bulk_reg)
connect(ptie, ptie_reg)
connect_global(bulk_reg, "SUBSTRATE")
soft_connect_global(ptie_reg, "SUBSTRATE")
# connect inside to "IOSUB" via polygons
connect(bulk, bulk_io)
connect(ptie, ptie_io)
connect(bulk_io, iosub)
soft_connect(ptie_io, iosub)
connect_global(iosub, "IOSUB")
# Netlist section (NOTE: we only check log here)
# for debugging: _make_soft_connection_diodes(true)
netlist
netlist.simplify