From 318efbf7b02fe282ceb13c7763aae36fa0d10fca Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 7 Nov 2019 22:54:16 +0100 Subject: [PATCH] Fixed 'scale_and_snap' feature --- src/db/db/dbCellVariants.cc | 129 +++++++++++++++++++++++-- src/db/db/dbCellVariants.h | 119 +++-------------------- src/db/db/dbLayoutUtils.cc | 21 +++- src/db/db/dbLayoutUtils.h | 2 +- src/db/db/dbRegionUtils.cc | 6 +- src/db/db/dbRegionUtils.h | 6 +- src/db/unit_tests/dbLayoutUtils.cc | 38 ++++++++ testdata/algo/layout_utils_au_sns1.gds | Bin 0 -> 111992 bytes testdata/algo/layout_utils_au_sns2.gds | Bin 0 -> 111992 bytes testdata/algo/layout_utils_au_sns3.gds | Bin 0 -> 111992 bytes testdata/algo/scale_and_snap.gds | Bin 0 -> 37590 bytes 11 files changed, 201 insertions(+), 120 deletions(-) create mode 100644 testdata/algo/layout_utils_au_sns1.gds create mode 100644 testdata/algo/layout_utils_au_sns2.gds create mode 100644 testdata/algo/layout_utils_au_sns3.gds create mode 100644 testdata/algo/scale_and_snap.gds diff --git a/src/db/db/dbCellVariants.cc b/src/db/db/dbCellVariants.cc index adb5b6e97..38456de6b 100644 --- a/src/db/db/dbCellVariants.cc +++ b/src/db/db/dbCellVariants.cc @@ -22,11 +22,124 @@ #include "dbCellVariants.h" +#include "dbRegionUtils.h" #include "tlUtils.h" namespace db { +// ------------------------------------------------------------------------------------------ + +db::ICplxTrans OrientationReducer::reduce (const db::ICplxTrans &trans) const +{ + db::ICplxTrans res (trans); + res.disp (db::Vector ()); + res.mag (1.0); + return res; +} + +db::Trans OrientationReducer::reduce (const db::Trans &trans) const +{ + return db::Trans (trans.fp_trans ()); +} + +// ------------------------------------------------------------------------------------------ + +db::ICplxTrans MagnificationReducer::reduce (const db::ICplxTrans &trans) const +{ + return db::ICplxTrans (trans.mag ()); +} + +db::Trans MagnificationReducer::reduce (const db::Trans &) const +{ + return db::Trans (); +} + +// ------------------------------------------------------------------------------------------ + +db::ICplxTrans MagnificationAndOrientationReducer::reduce (const db::ICplxTrans &trans) const +{ + db::ICplxTrans res (trans); + res.disp (db::Vector ()); + return res; +} + +db::Trans MagnificationAndOrientationReducer::reduce (const db::Trans &trans) const +{ + return db::Trans (trans.fp_trans ()); +} + +// ------------------------------------------------------------------------------------------ + +GridReducer::GridReducer (db::Coord grid) + : m_grid (grid) +{ + // .. nothing yet .. +} + +db::ICplxTrans GridReducer::reduce (const db::ICplxTrans &trans) const +{ + // NOTE: we need to keep magnification, angle and mirror so when combining the + // reduced transformations, the result will be equivalent to reducing the combined + // transformation. + db::ICplxTrans res (trans); + res.disp (db::Vector (trans.disp ().x () - snap_to_grid (trans.disp ().x (), m_grid), trans.disp ().y () - snap_to_grid (trans.disp ().y (), m_grid))); + return res; +} + +db::Trans GridReducer::reduce (const db::Trans &trans) const +{ + db::Trans res (trans); + res.disp (db::Vector (trans.disp ().x () - snap_to_grid (trans.disp ().x (), m_grid), trans.disp ().y () - snap_to_grid (trans.disp ().y (), m_grid))); + return res; +} + +// ------------------------------------------------------------------------------------------ + +ScaleAndGridReducer::ScaleAndGridReducer (db::Coord grid, db::Coord mult, db::Coord div) + : m_mult (mult), m_grid (int64_t (grid) * int64_t (div)) +{ + // .. nothing yet .. +} + +db::ICplxTrans ScaleAndGridReducer::reduce_trans (const db::ICplxTrans &trans) const +{ + db::ICplxTrans res (trans); + int64_t dx = int64_t (trans.disp ().x ()) * m_mult; + int64_t dy = int64_t (trans.disp ().y ()) * m_mult; + res.disp (db::Vector (db::Coord (dx - snap_to_grid (dx, m_grid)), db::Coord (dy - snap_to_grid (dy, m_grid)))); + return res; +} + +db::Trans ScaleAndGridReducer::reduce_trans (const db::Trans &trans) const +{ + db::Trans res (trans); + int64_t dx = int64_t (trans.disp ().x ()) * m_mult; + int64_t dy = int64_t (trans.disp ().y ()) * m_mult; + res.disp (db::Vector (db::Coord (dx - snap_to_grid (dx, m_grid)), db::Coord (dy - snap_to_grid (dy, m_grid)))); + return res; +} + +db::ICplxTrans ScaleAndGridReducer::reduce (const db::ICplxTrans &trans) const +{ + db::ICplxTrans res (trans); + int64_t dx = int64_t (trans.disp ().x ()); + int64_t dy = int64_t (trans.disp ().y ()); + res.disp (db::Vector (db::Coord (dx - snap_to_grid (dx, m_grid)), db::Coord (dy - snap_to_grid (dy, m_grid)))); + return res; +} + +db::Trans ScaleAndGridReducer::reduce (const db::Trans &trans) const +{ + db::Trans res (trans); + int64_t dx = int64_t (trans.disp ().x ()); + int64_t dy = int64_t (trans.disp ().y ()); + res.disp (db::Vector (db::Coord (dx - snap_to_grid (dx, m_grid)), db::Coord (dy - snap_to_grid (dy, m_grid)))); + return res; +} + +// ------------------------------------------------------------------------------------------ + VariantsCollectorBase::VariantsCollectorBase () : mp_red () { @@ -219,7 +332,7 @@ VariantsCollectorBase::commit_shapes (db::Layout &layout, db::Cell &top_cell, un for (db::CellInstArray::iterator ia = i->begin (); ! ia.at_end (); ++ia) { db::ICplxTrans t = i->complex_trans (*ia); - db::ICplxTrans rt = mp_red->reduce (vc->first * t); + db::ICplxTrans rt = mp_red->reduce (vc->first * mp_red->reduce_trans (t)); std::map::const_iterator v = vt.find (rt); if (v != vt.end ()) { @@ -263,7 +376,7 @@ VariantsCollectorBase::commit_shapes (db::Layout &layout, db::Cell &top_cell, un for (db::CellInstArray::iterator ia = i->begin (); ! ia.at_end (); ++ia) { db::ICplxTrans t = i->complex_trans (*ia); - db::ICplxTrans rt = mp_red->reduce (vvc.begin ()->first * t); + db::ICplxTrans rt = mp_red->reduce (vvc.begin ()->first * mp_red->reduce_trans (t)); std::map::const_iterator v = vt.find (rt); if (v != vt.end ()) { @@ -325,11 +438,11 @@ VariantsCollectorBase::add_variant_non_tl_invariant (std::mapreduce (inst.complex_trans (*i))] += 1; + variants [mp_red->reduce_trans (inst.complex_trans (*i))] += 1; } } else { for (db::CellInstArray::iterator i = inst.begin (); ! i.at_end (); ++i) { - variants [db::ICplxTrans (mp_red->reduce (*i))] += 1; + variants [db::ICplxTrans (mp_red->reduce_trans (*i))] += 1; } } } @@ -338,9 +451,9 @@ void VariantsCollectorBase::add_variant_tl_invariant (std::map &variants, const db::CellInstArray &inst) const { if (inst.is_complex ()) { - variants [mp_red->reduce (inst.complex_trans ())] += inst.size (); + variants [mp_red->reduce_trans (inst.complex_trans ())] += inst.size (); } else { - variants [db::ICplxTrans (mp_red->reduce (inst.front ()))] += inst.size (); + variants [db::ICplxTrans (mp_red->reduce_trans (inst.front ()))] += inst.size (); } } @@ -390,7 +503,7 @@ VariantsCollectorBase::create_var_instances_non_tl_invariant (db::Cell &in_cell, for (db::CellInstArray::iterator ia = i->begin (); ! ia.at_end (); ++ia) { - db::ICplxTrans rt = mp_red->reduce (for_var * i->complex_trans (*ia)); + db::ICplxTrans rt = mp_red->reduce (for_var * mp_red->reduce_trans (i->complex_trans (*ia))); std::map::const_iterator v = vt.find (rt); tl_assert (v != vt.end ()); @@ -419,7 +532,7 @@ VariantsCollectorBase::create_var_instances_tl_invariant (db::Cell &in_cell, std std::map::const_iterator v; - db::ICplxTrans rt = mp_red->reduce (for_var * i->complex_trans ()); + db::ICplxTrans rt = mp_red->reduce (for_var * mp_red->reduce_trans (i->complex_trans ())); v = vt.find (rt); tl_assert (v != vt.end ()); diff --git a/src/db/db/dbCellVariants.h b/src/db/db/dbCellVariants.h index 34775bc4a..965bdae5f 100644 --- a/src/db/db/dbCellVariants.h +++ b/src/db/db/dbCellVariants.h @@ -50,6 +50,8 @@ public: TransformationReducer () { } virtual ~TransformationReducer () { } + virtual db::Trans reduce_trans (const db::Trans &trans) const { return reduce (trans); } + virtual db::ICplxTrans reduce_trans (const db::ICplxTrans &trans) const { return reduce (trans); } virtual db::Trans reduce (const db::Trans &trans) const = 0; virtual db::ICplxTrans reduce (const db::ICplxTrans &trans) const = 0; virtual bool is_translation_invariant () const { return true; } @@ -63,18 +65,8 @@ public: struct DB_PUBLIC OrientationReducer : public TransformationReducer { - db::ICplxTrans reduce (const db::ICplxTrans &trans) const - { - db::ICplxTrans res (trans); - res.disp (db::Vector ()); - res.mag (1.0); - return res; - } - - db::Trans reduce (const db::Trans &trans) const - { - return db::Trans (trans.fp_trans ()); - } + db::ICplxTrans reduce (const db::ICplxTrans &trans) const; + db::Trans reduce (const db::Trans &trans) const; }; /** @@ -85,15 +77,8 @@ struct DB_PUBLIC OrientationReducer struct DB_PUBLIC MagnificationReducer : public TransformationReducer { - db::ICplxTrans reduce (const db::ICplxTrans &trans) const - { - return db::ICplxTrans (trans.mag ()); - } - - db::Trans reduce (const db::Trans &) const - { - return db::Trans (); - } + db::ICplxTrans reduce (const db::ICplxTrans &trans) const; + db::Trans reduce (const db::Trans &) const; }; /** @@ -104,17 +89,8 @@ struct DB_PUBLIC MagnificationReducer struct DB_PUBLIC MagnificationAndOrientationReducer : public TransformationReducer { - db::ICplxTrans reduce (const db::ICplxTrans &trans) const - { - db::ICplxTrans res (trans); - res.disp (db::Vector ()); - return res; - } - - db::Trans reduce (const db::Trans &trans) const - { - return db::Trans (trans.fp_trans ()); - } + db::ICplxTrans reduce (const db::ICplxTrans &trans) const; + db::Trans reduce (const db::Trans &trans) const; }; /** @@ -125,47 +101,15 @@ struct DB_PUBLIC MagnificationAndOrientationReducer struct DB_PUBLIC GridReducer : public TransformationReducer { - GridReducer (db::Coord grid) - : m_grid (grid) - { - // .. nothing yet .. - } + GridReducer (db::Coord grid); - db::ICplxTrans reduce (const db::ICplxTrans &trans) const - { - // NOTE: we need to keep magnification, angle and mirror so when combining the - // reduced transformations, the result will be equivalent to reducing the combined - // transformation. - db::ICplxTrans res (trans); - res.disp (db::Vector (mod (trans.disp ().x ()), mod (trans.disp ().y ()))); - return res; - } - - db::Trans reduce (const db::Trans &trans) const - { - db::Trans res (trans); - res.disp (db::Vector (mod (trans.disp ().x ()), mod (trans.disp ().y ()))); - return res; - } + db::ICplxTrans reduce (const db::ICplxTrans &trans) const; + db::Trans reduce (const db::Trans &trans) const; bool is_translation_invariant () const { return false; } private: db::Coord m_grid; - - inline db::Coord mod (db::Coord c) const - { - if (c < 0) { - c = m_grid - (-c) % m_grid; - if (c == m_grid) { - return 0; - } else { - return c; - } - } else { - return c % m_grid; - } - } }; /** @@ -178,49 +122,18 @@ private: struct DB_PUBLIC ScaleAndGridReducer : public TransformationReducer { - ScaleAndGridReducer (db::Coord grid, db::Coord mult, db::Coord div) - : m_mult (mult), m_grid (int64_t (grid) * int64_t (div)) - { - // .. nothing yet .. - } + ScaleAndGridReducer (db::Coord grid, db::Coord mult, db::Coord div); - db::ICplxTrans reduce (const db::ICplxTrans &trans) const - { - // NOTE: we need to keep magnification, angle and mirror so when combining the - // reduced transformations, the result will be equivalent to reducing the combined - // transformation. - db::ICplxTrans res (trans); - res.disp (db::Vector (mod (trans.disp ().x ()), mod (trans.disp ().y ()))); - return res; - } - - db::Trans reduce (const db::Trans &trans) const - { - db::Trans res (trans); - res.disp (db::Vector (mod (trans.disp ().x ()), mod (trans.disp ().y ()))); - return res; - } + virtual db::ICplxTrans reduce_trans (const db::ICplxTrans &trans) const; + virtual db::Trans reduce_trans (const db::Trans &trans) const; + virtual db::ICplxTrans reduce (const db::ICplxTrans &trans) const; + virtual db::Trans reduce (const db::Trans &trans) const; bool is_translation_invariant () const { return false; } private: int64_t m_mult; int64_t m_grid; - - inline db::Coord mod (db::Coord c) const - { - int64_t cc = int64_t (c) * m_mult; - if (cc < 0) { - cc = m_grid - (-cc) % m_grid; - if (cc == m_grid) { - return 0; - } else { - return db::Coord (cc); - } - } else { - return db::Coord (cc % m_grid); - } - } }; /** diff --git a/src/db/db/dbLayoutUtils.cc b/src/db/db/dbLayoutUtils.cc index 0b8bc636c..580c4066d 100644 --- a/src/db/db/dbLayoutUtils.cc +++ b/src/db/db/dbLayoutUtils.cc @@ -493,7 +493,20 @@ scale_and_snap (db::Layout &layout, db::Cell &cell, db::Coord g, db::Coord m, db db::Polygon poly; si->polygon (poly); poly.transform (tr); - out.insert (scaled_and_snapped_polygon (poly, g, m, d, tr_disp.x (), g, m, d, tr_disp.y (), heap).transformed (trinv)); + poly = scaled_and_snapped_polygon (poly, g, m, d, tr_disp.x (), g, m, d, tr_disp.y (), heap); + poly.transform (trinv); + out.insert (poly); + + } + + for (db::Shapes::shape_iterator si = s.begin (db::ShapeIterator::Texts); ! si.at_end (); ++si) { + + db::Text text; + si->text (text); + text.transform (tr); + text.trans (db::Trans (text.trans ().rot (), scaled_and_snapped_vector (text.trans ().disp (), g, m, d, tr_disp.x (), g, m, d, tr_disp.y ()))); + text.transform (trinv); + out.insert (text); } @@ -515,7 +528,11 @@ scale_and_snap (db::Layout &layout, db::Cell &cell, db::Coord g, db::Coord m, db for (db::CellInstArray::iterator i = ia.begin (); ! i.at_end (); ++i) { db::Trans ti (*i); - ti.disp (scaled_and_snapped_vector (ti.disp (), g, m, d, g, m, d)); + db::Vector ti_disp = ti.disp (); + ti_disp.transform (tr); + ti_disp = scaled_and_snapped_vector (ti_disp, g, m, d, tr_disp.x (), g, m, d, tr_disp.y ()); + ti_disp.transform (trinv); + ti.disp (ti_disp); if (ia.is_complex ()) { new_insts.push_back (db::CellInstArray (ia.object (), ia.complex_trans (ti))); diff --git a/src/db/db/dbLayoutUtils.h b/src/db/db/dbLayoutUtils.h index 44e176ad6..2c6cbd1ae 100644 --- a/src/db/db/dbLayoutUtils.h +++ b/src/db/db/dbLayoutUtils.h @@ -229,7 +229,7 @@ private: * This method will scale and snap all layers from the given cell and below to the * specified grid. Scaling happens by the rational factor m / d. */ -void scale_and_snap (db::Layout &layout, db::Cell &cell, db::Coord g, db::Coord m, db::Coord d); +DB_PUBLIC void scale_and_snap (db::Layout &layout, db::Cell &cell, db::Coord g, db::Coord m, db::Coord d); } // namespace db diff --git a/src/db/db/dbRegionUtils.cc b/src/db/db/dbRegionUtils.cc index 98475d9a0..e2b975f2f 100644 --- a/src/db/db/dbRegionUtils.cc +++ b/src/db/db/dbRegionUtils.cc @@ -427,13 +427,13 @@ scaled_and_snapped_polygon (const db::Polygon &poly, db::Coord gx, db::Coord mx, } db::Vector -scaled_and_snapped_vector (const db::Vector &v, db::Coord gx, db::Coord mx, db::Coord dx, db::Coord gy, db::Coord my, db::Coord dy) +scaled_and_snapped_vector (const db::Vector &v, db::Coord gx, db::Coord mx, db::Coord dx, db::Coord ox, db::Coord gy, db::Coord my, db::Coord dy, db::Coord oy) { int64_t dgx = int64_t (gx) * int64_t (dx); int64_t dgy = int64_t (gy) * int64_t (dy); - int64_t x = snap_to_grid (int64_t (v.x ()) * mx, dgx) / int64_t (dx); - int64_t y = snap_to_grid (int64_t (v.y ()) * my, dgy) / int64_t (dy); + int64_t x = snap_to_grid (int64_t (v.x ()) * mx + int64_t (ox), dgx) / int64_t (dx); + int64_t y = snap_to_grid (int64_t (v.y ()) * my + int64_t (oy), dgy) / int64_t (dy); return db::Vector (db::Coord (x), db::Coord (y)); } diff --git a/src/db/db/dbRegionUtils.h b/src/db/db/dbRegionUtils.h index 95fd8e5ea..dd59d41f6 100644 --- a/src/db/db/dbRegionUtils.h +++ b/src/db/db/dbRegionUtils.h @@ -545,15 +545,15 @@ DB_PUBLIC db::Polygon snapped_polygon (const db::Polygon &poly, db::Coord gx, db /** * @brief Scales and snaps a polygon to the given grid * Heap is a vector of points reused for the point list - * The coordinate transformation is q = ((p * m + o) % (g * d)) / d. + * The coordinate transformation is q = ((p * m + o) snap (g * d)) / d. */ DB_PUBLIC db::Polygon scaled_and_snapped_polygon (const db::Polygon &poly, db::Coord gx, db::Coord mx, db::Coord dx, db::Coord ox, db::Coord gy, db::Coord my, db::Coord dy, db::Coord oy, std::vector &heap); /** * @brief Scales and snaps a vector to the given grid - * The coordinate transformation is q = ((p * m) % (g * d)) / d. + * The coordinate transformation is q = ((p * m + o) snap (g * d)) / d. */ -DB_PUBLIC db::Vector scaled_and_snapped_vector (const db::Vector &v, db::Coord gx, db::Coord mx, db::Coord dx, db::Coord gy, db::Coord my, db::Coord dy); +DB_PUBLIC db::Vector scaled_and_snapped_vector (const db::Vector &v, db::Coord gx, db::Coord mx, db::Coord dx, db::Coord ox, db::Coord gy, db::Coord my, db::Coord dy, db::Coord oy); } // namespace db diff --git a/src/db/unit_tests/dbLayoutUtils.cc b/src/db/unit_tests/dbLayoutUtils.cc index 57b73bb94..1c7ce3825 100644 --- a/src/db/unit_tests/dbLayoutUtils.cc +++ b/src/db/unit_tests/dbLayoutUtils.cc @@ -615,3 +615,41 @@ TEST(16) } +TEST(17_scale_and_snap) +{ + db::Layout l1; + { + std::string fn (tl::testsrc ()); + fn += "/testdata/algo/scale_and_snap.gds"; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (l1); + } + + db::scale_and_snap (l1, l1.cell (*l1.begin_top_down ()), 1, 20, 19); + + CHECKPOINT(); + db::compare_layouts (_this, l1, tl::testsrc () + "/testdata/algo/layout_utils_au_sns1.gds"); + + db::scale_and_snap (l1, l1.cell (*l1.begin_top_down ()), 1, 19, 20); + + CHECKPOINT(); + db::compare_layouts (_this, l1, tl::testsrc () + "/testdata/algo/layout_utils_au_sns2.gds"); +} + +TEST(18_scale_and_snap) +{ + db::Layout l1; + { + std::string fn (tl::testsrc ()); + fn += "/testdata/algo/scale_and_snap.gds"; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (l1); + } + + db::scale_and_snap (l1, l1.cell (*l1.begin_top_down ()), 19, 1, 1); + + CHECKPOINT(); + db::compare_layouts (_this, l1, tl::testsrc () + "/testdata/algo/layout_utils_au_sns3.gds"); +} diff --git a/testdata/algo/layout_utils_au_sns1.gds b/testdata/algo/layout_utils_au_sns1.gds new file mode 100644 index 0000000000000000000000000000000000000000..372770c6179876eb4874868741fed3acf416c375 GIT binary patch literal 111992 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 X(uL!$^6@ra06pbi@QS>W{BrmI&i0vh literal 0 HcmV?d00001 diff --git a/testdata/algo/layout_utils_au_sns2.gds b/testdata/algo/layout_utils_au_sns2.gds new file mode 100644 index 0000000000000000000000000000000000000000..1a69e3ee5e9e63791e335196cadc12c092d2bb7b GIT binary patch literal 111992 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 V?g}4o;|0)@^@6AJO7hFy{{y;cX#oHL literal 0 HcmV?d00001 diff --git a/testdata/algo/scale_and_snap.gds b/testdata/algo/scale_and_snap.gds new file mode 100644 index 0000000000000000000000000000000000000000..9a1972426d0c755cf1a781557e720c41bce7e422 GIT binary patch literal 37590 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 literal 0 HcmV?d00001