Merge pull request #2089 from KLayout/bugfix/issue-2087

Bugfix/issue 2087
This commit is contained in:
Matthias Köfferlein 2025-07-19 18:57:25 +02:00 committed by GitHub
commit ff7d7f20ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 276 additions and 29 deletions

View File

@ -77,14 +77,16 @@ public:
}
// because the rasterizer can't handle overlapping cells we need to multiply the row and columns steps
// with an integer until the effective rasterizer pitch get big enough.
// with an integer until the effective rasterizer pitch gets big enough.
m_row_steps *= (m_dim.x () - 1) / (m_row_steps * m_row_step.x ()) + 1;
m_column_steps *= (m_dim.y () - 1) / (m_column_steps * m_column_step.y ()) + 1;
db::Box fp_bbox = fp.box ();
// compensate for distortion by sheared kernel
fp_bbox.enlarge (db::Vector (db::coord_traits<db::Coord>::rounded (double (fp_bbox.height ()) * std::abs (m_column_step.x ()) / dy), db::coord_traits<db::Coord>::rounded (double (fp_bbox.width ()) * std::abs (m_row_step.y ()) / dx)));
db::Coord ex = std::max (std::abs (db::Coord (m_column_step.x () * m_column_steps)), std::abs (db::Coord (m_row_step.x () * m_row_steps)));
db::Coord ey = std::max (std::abs (db::Coord (m_column_step.y () * m_column_steps)), std::abs (db::Coord (m_row_step.y () * m_row_steps)));
fp_bbox.enlarge (db::Vector (ex, ey));
int columns_per_rows = (int (m_row_steps) * m_row_step.y ()) / dy;
int rows_per_columns = (int (m_column_steps) * m_column_step.x ()) / dx;
@ -167,6 +169,11 @@ public:
return m_area_maps [i];
}
db::AreaMap &area_map (unsigned int i)
{
return m_area_maps [i];
}
private:
std::vector<db::AreaMap> m_area_maps;
db::Vector m_row_step, m_column_step;
@ -246,7 +253,7 @@ fill_polygon_impl (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type f
for (unsigned int i = 0; i < am.area_maps (); ++i) {
const db::AreaMap &am1 = am.area_map (i);
db::AreaMap &am1 = am.area_map (i);
size_t nx = am1.nx ();
size_t ny = am1.ny ();
@ -263,31 +270,54 @@ fill_polygon_impl (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type f
++jj;
}
ninsts += (jj - j);
db::Vector p0 = (am1.p0 () - db::Point ()) - kernel_origin;
p0 += db::Vector (i * am1.d ().x (), j * am1.d ().y ());
db::CellInstArray array;
if (jj > j + 1) {
array = db::CellInstArray (db::CellInst (fill_cell_index), db::Trans (p0), db::Vector (0, am1.d ().y ()), db::Vector (), (unsigned long) (jj - j), 1);
// try to expand the array in x direction
size_t ii = i + 1;
for ( ; ii < nx; ++ii) {
bool all = true;
for (size_t k = j; k < jj && all; ++k) {
all = am1.get (ii, k) == am1.pixel_area ();
}
if (all) {
for (size_t k = j; k < jj; ++k) {
// disable pixel, so we do not see it again in the following columns
am1.get (ii, k) = 0;
}
} else {
break;
}
}
ninsts += (jj - j) * (ii - i);
if (jj > j + 1 || ii > i + 1) {
array = db::CellInstArray (db::CellInst (fill_cell_index), db::Trans (p0), db::Vector (0, am1.d ().y ()), db::Vector (am1.d ().x (), 0), (unsigned long) (jj - j), (unsigned long) (ii - i));
} else {
array = db::CellInstArray (db::CellInst (fill_cell_index), db::Trans (p0));
}
{
// In case we run this from a tiling processor we need to lock against multithread races
tl::MutexLocker locker (&db::TilingProcessor::output_lock ());
tl_assert (cell->layout () != 0);
tl::MutexLocker locker (&cell->layout ()->lock ());
cell->insert (array);
}
if (remaining_parts) {
if (am1.d ().y () == am1.p ().y ()) {
filled_regions.push_back (db::Polygon (db::Box (db::Point (), db::Point (am1.p ().x (), am1.p ().y () * db::Coord (jj - j))).moved (kernel_origin + p0)));
if (am1.d ().y () == am1.p ().y () && am1.d ().x () == am1.p ().x ()) {
db::Box fill_box (db::Point (), db::Point (am1.p ().x () * db::Coord (ii - i), am1.p ().y () * db::Coord (jj - j)));
filled_regions.push_back (db::Polygon (fill_box.enlarged (fill_margin).moved (kernel_origin + p0)));
} else {
db::Box fill_box (db::Point (), db::Point () + am1.p ());
fill_box.enlarge (fill_margin);
for (size_t k = 0; k < jj - j; ++k) {
filled_regions.push_back (db::Polygon (db::Box (db::Point (), db::Point () + am1.p ()).moved (kernel_origin + p0 + db::Vector (0, am1.d ().y () * db::Coord (k)))));
for (size_t l = 0; l < ii - i; ++l) {
filled_regions.push_back (db::Polygon (fill_box.moved (kernel_origin + p0 + db::Vector (am1.d ().x () * db::Coord (l), am1.d ().y () * db::Coord (k)))));
}
}
}
}
@ -314,19 +344,9 @@ fill_polygon_impl (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type f
if (any_fill) {
if (remaining_parts) {
std::vector <db::Polygon> fp1;
if (fill_margin != db::Vector ()) {
ep.size (filled_regions, fill_margin.x (), fill_margin.y (), fp1, 3 /*mode*/, false /*=don't resolve holes*/);
filled_regions.swap (fp1);
fp1.clear ();
}
fp1.push_back (fp0);
ep.boolean (fp1, filled_regions, *remaining_parts, db::BooleanOp::ANotB, false /*=don't resolve holes*/);
}
return true;

View File

@ -1918,7 +1918,7 @@ rasterize_impl (const db::polygon<C> &polygon, db::area_map<C> &am)
area_type aa = a;
if (dx == py) {
if (dx == px) {
box_type cell (x, y, xx, yy);

View File

@ -500,15 +500,21 @@ RecursiveShapeIterator::validate (RecursiveShapeReceiver *receiver) const
}
if (mp_shapes) {
// Ensures the trees are built properly - this is important in MT contexts (i.e. TilingProcessor)
// TODO: get rid of that const cast
(const_cast <db::Shapes *> (mp_shapes))->update ();
start_shapes ();
} else if (mp_layout && (! m_has_layers || m_current_layer < m_layers.size ())) {
// Ensures the trees are built properly - this is important in MT contexts (i.e. TilingProcessor)
mp_layout->update ();
new_cell (receiver);
next_shape (receiver);
}
if (mp_layout && ! at_end ()) {

View File

@ -1127,10 +1127,20 @@ void Shapes::reset_bbox_dirty ()
void Shapes::update ()
{
std::unique_ptr<tl::MutexLocker> locker;
// If not in a layout context, we should lock here against multiple calls from different threads.
// In a layout context, the Layout object will do that for us.
if (layout () == 0) {
static tl::Mutex lock;
locker.reset (new tl::MutexLocker (&lock));
}
for (tl::vector<LayerBase *>::const_iterator l = m_layers.begin (); l != m_layers.end (); ++l) {
(*l)->sort ();
(*l)->update_bbox ();
}
set_dirty (false);
}

View File

@ -345,3 +345,35 @@ TEST(5)
CHECKPOINT();
db::compare_layouts (_this, ly, tl::testdata () + "/algo/fill_tool_au5.oas", db::WriteOAS);
}
// issue #2087
TEST(6)
{
db::Layout ly;
{
std::string fn (tl::testdata ());
fn += "/algo/fill_tool6.gds";
tl::InputStream stream (fn);
db::Reader reader (stream);
reader.read (ly);
}
db::cell_index_type fill_cell = ly.cell_by_name ("FILL_CELL").second;
db::cell_index_type top_cell = ly.cell_by_name ("TOP").second;
unsigned int fill_layer = ly.get_layer (db::LayerProperties (1, 0));
db::Region fill_region (db::RecursiveShapeIterator (ly, ly.cell (top_cell), fill_layer));
db::Region remaining_polygons;
db::Vector rs (2500, 0);
db::Vector cs (650, 2500);
db::Box fc_box = ly.cell (fill_cell).bbox ();
db::fill_region (&ly.cell (top_cell), fill_region, fill_cell, fc_box, rs, cs, db::Point (), false, &remaining_polygons);
unsigned int l100 = ly.insert_layer (db::LayerProperties (100, 0));
remaining_polygons.insert_into (&ly, top_cell, l100);
CHECKPOINT();
db::compare_layouts (_this, ly, tl::testdata () + "/algo/fill_tool_au6.oas", db::WriteOAS);
}

View File

@ -5460,8 +5460,17 @@ CODE
# as the reference point. The reference point will also defined the footprint of the fill cell - more precisely
# the lower left corner. When step vectors are given, the fill cell's footprint is taken to be a rectangle
# having the horizontal and vertical step pitch for width and height respectively. This way the fill cells
# will be arrange seamlessly. However, the cell's dimensions can be changed, so that the fill cells
# will be arrange seamlessly.
#
# However, the cell's dimensions can be changed, so that the fill cells
# can overlap or there is a space between the cells. To change the dimensions use the "dim" method.
# This example will use a fill cell footprint of 1x1 micrometers, regardless of the step pitch:
#
# @code
# p = fill_pattern("FILL_CELL")
# p.shape(1, 0, box(0.0, 0.0, 1.0, 1.0))
# p.dim(1.0, 1.0)
# @/code
#
# The following example specifies a fill cell with an active area of -0.5 .. 1.5 in both directions
# (2 micron width and height). With these dimensions the fill cell's footprint is independent of the
@ -5474,6 +5483,18 @@ CODE
# p.dim(2.0, 2.0)
# @/code
#
# Finally, the fill cell can be given a margin: this is a space around the fill cell which needs
# to be inside the fill region. Hence, the margin can be used to implement a distance, the fill
# cells (more precisely: their footprints) will maintain to the outside border of the fill region.
# The following example implements a margin of 200 nm in horizontal and 250 nm in vertical direction:
#
# @code
# p = fill_pattern("FILL_CELL")
# p.shape(1, 0, box(0.0, 0.0, 1.0, 1.0))
# p.dim(1.0, 1.0)
# p.margin(0.2, 0.25)
# @/code
#
# With these ingredients will can use the fill function. The first example fills the polygons
# of "to_fill" with an orthogonal pattern of 1x1 micron rectangles with a pitch of 2 microns:
#
@ -5574,6 +5595,7 @@ CODE
fill_cell = pattern.create_cell(@engine._output_layout, @engine)
top_cell = @engine._output_cell
fc_box = dbu_trans * pattern.cell_box(row_step.x, column_step.y)
fill_margin = dbu_trans * pattern.fill_margin
rs = dbu_trans * row_step
cs = dbu_trans * column_step
origin = origin ? dbu_trans * origin : nil
@ -5604,6 +5626,7 @@ CODE
tp.var("rs", rs)
tp.var("cs", cs)
tp.var("origin", origin)
tp.var("fill_margin", fill_margin)
tp.var("fc_index", fc_index)
tp.var("repeat", repeat)
tp.var("with_left", with_left)
@ -5616,8 +5639,8 @@ CODE
tile_box = tile_box & tc_box;
var left = with_left ? Region.new : nil;
repeat ?
(region & tile_box).fill_multi(top_cell, fc_index, fc_box, rs, cs, Vector.new, left, _tile.bbox) :
(region & tile_box).fill(top_cell, fc_index, fc_box, rs, cs, origin, left, Vector.new, left, _tile.bbox);
(region & tile_box).fill_multi(top_cell, fc_index, fc_box, rs, cs, fill_margin, left, _tile.bbox) :
(region & tile_box).fill(top_cell, fc_index, fc_box, rs, cs, origin, left, fill_margin, left, _tile.bbox);
with_left && _output(#{result_arg}, left)
)
END
@ -5639,9 +5662,9 @@ END
@engine.run_timed("\"#{m}\" in: #{@engine.src_line}", self.data) do
if repeat
self.data.fill_multi(top_cell, fc_index, fc_box, rs, cs, RBA::Vector::new, result)
self.data.fill_multi(top_cell, fc_index, fc_box, rs, cs, fill_margin, result)
else
self.data.fill(top_cell, fc_index, fc_box, rs, cs, origin, result, RBA::Vector::new, result)
self.data.fill(top_cell, fc_index, fc_box, rs, cs, origin, result, fill_margin, result)
end
end

View File

@ -335,6 +335,7 @@ module DRC
@shapes = []
@origin = nil
@dim = nil
@margin = RBA::DVector::new
end
def create_cell(layout, engine)
@ -350,7 +351,11 @@ module DRC
def cell_box(def_w, def_h)
o = @origin || self._computed_origin
d = @dim || RBA::DVector::new(def_w, def_h)
RBA::DBox::new(o, o + d)
RBA::DBox::new(o, o + d).enlarged(@margin)
end
def fill_margin
-@margin
end
def default_xpitch
@ -454,6 +459,20 @@ module DRC
end
def margin(w, h)
if !w.is_a?(1.class) && !w.is_a?(1.0.class)
raise("w argument not numeric FillCell#dim")
end
if !h.is_a?(1.class) && !h.is_a?(1.0.class)
raise("h argument not numeric FillCell#dim")
end
@margin = RBA::DVector::new(w, h)
self
end
end
# A wrapper for the fill step definition

View File

@ -0,0 +1,70 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "tlUnitTest.h"
#include "dbReader.h"
#include "dbTestSupport.h"
#include "lymMacro.h"
TEST(1_IHPMetal1Fill)
{
test_is_long_runner ();
std::string rs = tl::testdata ();
rs += "/drc/drcFullTest_1.drc";
std::string input = tl::testdata ();
input += "/drc/drcFullTest_1.oas";
std::string au = tl::testdata ();
au += "/drc/drcFullTest_au1.oas";
std::string output = this->tmp_file ("tmp.oas");
{
// Set some variables
lym::Macro config;
config.set_text (tl::sprintf (
"$drc_force_gc = true\n"
"$drc_test_source = '%s'\n"
"$drc_test_target = '%s'\n"
, input, output)
);
config.set_interpreter (lym::Macro::Ruby);
EXPECT_EQ (config.run (), 0);
}
lym::Macro drc;
drc.load_from (rs);
EXPECT_EQ (drc.run (), 0);
db::Layout layout;
{
tl::InputStream stream (output);
db::Reader reader (stream);
reader.read (layout);
}
db::compare_layouts (_this, layout, au, db::NoNormalization);
}

View File

@ -10,6 +10,7 @@ SOURCES = \
drcBasicTests.cc \
drcGenericTests.cc \
drcSimpleTests.cc \
drcFullTests.cc \
drcSuiteTests.cc \
INCLUDEPATH += $$DRC_INC $$TL_INC $$RDB_INC $$DB_INC $$GSI_INC $$LYM_INC

BIN
testdata/algo/fill_tool6.gds vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
testdata/algo/fill_tool_au6.oas vendored Normal file

Binary file not shown.

58
testdata/drc/drcFullTest_1.drc vendored Normal file
View File

@ -0,0 +1,58 @@
source($drc_test_source)
target($drc_test_target)
verbose
ncpu = 4
chip = input(189, 4)
chip.output(189, 4)
# NOTE: this must not happen in tiled mode as the sealring
# is only visible as a whole in flat mode
sealring = source.cell("sealring")
metal1_seal = sealring.input(8, 0)
metal1_seal_inner = metal1_seal.holes
# NOTE: metal1_seal_outer is empty if there is no sealring ->
# the full chip will be filled
metal1_seal_outer = chip.interacting(metal1_seal_inner) - metal1_seal_inner
# Everything else can be done in tiled mode
tiles(500)
tile_borders(2.0)
threads(ncpu)
metal1 = input(8, 0)
metal1.output(8, 0)
metal1_fill = input(8, 22)
metal1_nofill = input(8, 23) + metal1_seal_outer
metal1_dist = 0.42
min_space_to_fill = 1.0
pattern = fill_pattern("METAL1_FILL1")
pattern.shape(8, 22, box(0.0, 0.0, 5.0, 5.0))
pattern.dim(5.0, 5.0)
pattern.margin(metal1_dist, metal1_dist)
to_fill = chip - metal1_nofill - metal1
to_fill = to_fill.fill_with_left(pattern, hstep(7.0, 0), vstep(1.5, 7.0), multi_origin)
pattern = fill_pattern("METAL1_FILL2")
pattern.shape(8, 22, box(0.0, 0.0, 2.0, 2.0))
pattern.dim(2.0, 2.0)
pattern.margin(metal1_dist, metal1_dist)
to_fill = to_fill.fill_with_left(pattern, hstep(2.42, 0), vstep(0.65, 2.42), multi_origin)
pattern = fill_pattern("METAL1_FILL3")
pattern.shape(8, 22, box(0.0, 0.0, 1.2, 1.2))
pattern.dim(1.2, 1.2)
pattern.margin(metal1_dist, metal1_dist)
to_fill = to_fill.fill_with_left(pattern, hstep(1.62, 0), vstep(0.3, 1.62), multi_origin)

BIN
testdata/drc/drcFullTest_1.oas vendored Normal file

Binary file not shown.

BIN
testdata/drc/drcFullTest_au1.oas vendored Normal file

Binary file not shown.

View File

@ -14,18 +14,22 @@ f2 = extent - l1.sized(1.0)
p1 = fill_pattern("PAT1").shape(100, 0, box(0, 0, 1.um, 1.um)).origin(-0.5.um, -0.5.um)
p2 = fill_pattern("PAT2").shape(100, 1, box(0, 0, 1.um, 1.um)).origin(-0.5.um, -0.5.um)
p3 = fill_pattern("PAT3").shape(100, 2, box(0, 0, 1.um, 1.um)).shape(1000, 0, box(-0.5.um, -0.5.um, 1.5.um, 1.5.um))
p4 = fill_pattern("PAT4").shape(100, 3, box(0, 0, 1.um, 1.um)).dim(1.um, 1.um).margin(2.um, 4.um)
p11 = fill_pattern("PAT11").shape(101, 0, box(0, 0, 1.um, 1.um)).origin(-0.5.um, -0.5.um)
p12 = fill_pattern("PAT12").shape(101, 1, box(0, 0, 1.um, 1.um)).origin(-0.5.um, -0.5.um)
p13 = fill_pattern("PAT13").shape(101, 2, box(0, 0, 1.um, 1.um)).shape(1000, 0, box(-0.5.um, -0.5.um, 1.5.um, 1.5.um))
p14 = fill_pattern("PAT14").shape(101, 3, box(0, 0, 1.um, 1.um)).dim(1.um, 1.um).margin(2.um, 4.um)
f1.fill(p1, hstep(2.0, 1.0), vstep(-1.0, 2.0))
f1.fill(p2, hstep(2.0, 1.0), vstep(-1.0, 2.0), auto_origin)
f1.fill(p3)
f1.fill(p4, hstep(2.0, 0), vstep(0, 2.0))
f2.fill(p11, hstep(2.0, 1.0), vstep(-1.0, 2.0))
f2.fill(p12, hstep(2.0, 1.0), vstep(-1.0, 2.0), auto_origin)
f2.fill(p13)
f2.fill(p14, hstep(2.0, 0), vstep(0, 2.0))
l1.output(1, 0)
f1.output(10, 0)

View File

@ -11,8 +11,12 @@ l1 = input(1, 0)
f1 = l1
p1 = fill_pattern("PAT1").shape(100, 0, box(0, 0, 1.um, 1.um)).origin(-0.5.um, -0.5.um)
p2 = fill_pattern("PAT1").shape(100, 1, box(0, 0, 1.um, 1.um)).dim(1.um, 1.um)
p3 = fill_pattern("PAT1").shape(100, 2, box(0, 0, 1.um, 1.um)).dim(1.um, 1.um).margin(1.um, 2.um)
f1.fill_with_left(p1, hstep(2.0, 1.0), vstep(-1.0, 2.0)).output(100, 0)
f1.fill_with_left(p1, hstep(2.0, 1.0), vstep(-1.0, 2.0)).output(101, 0)
f1.fill_with_left(p2, hstep(2.0, 1.0), vstep(-1.0, 2.0)).output(101, 1)
f1.fill_with_left(p3, hstep(2.0, 1.0), vstep(-1.0, 2.0)).output(101, 2)
l1.output(1, 0)
f1.output(10, 0)

Binary file not shown.

Binary file not shown.