Repository: KLayout/klayout (language: python)

Issue:
read lef/def feature request: issue WARNING if VIA SPACING is an odd number
This is a feature request triggered by https://github.com/KLayout/klayout/issues/1877

When reading lef, an odd number for VIARULE..LAYER..SPACING will lead to rounding issues.
That's because via geometry has to be centered and thus there will be a divide by two used in the calculation of the anchor point of the top/bottom rectangle.

Issue a WARNING if the lef VIARULE..LAYER..SPACING is an odd number.
Issue a WARNING if the def VIA + CUTSPACING is an odd number.
Bug VIARULE GENERATE in DEF to GDS conversion in ORFS using Klayout
Greeting! 👋

### Issue: 

**GDS file generated by KLayout shows mismatches with the DEF file, leading to DRC violations in VIA ARRAY Cells (VIA-METAL1 and VIA-METAL2).**

both KLayout 0.28.8, 0.28.5 and 0.28.8 (From latest commit of ORFS).


### Observations:

- DEF file passes DRC checks, while the GDS file from KLayout has issues.
- Layer mapping has been verified.
- DBU is set to 0.001 DBU, and Tech LEF DATABASE MICROS is set to 1000.

![image](https://github.com/user-attachments/assets/0fb3cb4f-9f48-41f7-b0e5-4ad0cf325faa)

Note: the numbers are not exactly what I'm using for NDA purposes. They were useful in illustrating the problem in my [Slack Discussion](https://web.open-source-silicon.dev/t/16671207/hello-everyone-i-need-some-assistance-with-critical-issues-i)

### Minimal reproducible:

The TECH LEF Snippet that produces the problem (a shorter via overhangs on the right-most generated via) that cause DRC issues, I've added the edit the solves the problems by increasing the overhang (so the shorter version with the bug doesn't cause a DRC error. 

```
... 

DATABASE MICRONS 1000 
MANUFACTURINGGRID 0.001 

....

# Original: DRC ERRORS
# VIARULE MYVIA1_ARRAY GENERATE
#     LAYER METAL_1 ;
#         ENCLOSURE 0.061 0.007 ;
#     LAYER METAL_2 ;
#         ENCLOSURE 0.061 0.007 ;
#     LAYER VIA_1 ;
#         RECT -0.1XX -0.1XX 0.1XX 0.1XX ;
#         SPACING 0.4XX BY 0.4XX ;
# END MYVIA1_ARRAY


# Edit: Increase overhangs as a workaround 
VIARULE MYVIA1_ARRAY GENERATE
    LAYER METAL_1 ;
        ENCLOSURE 0.062 0.007 ;
    LAYER METAL_2 ;
        ENCLOSURE 0.061 0.008 ;
    LAYER VIA_1 ;
        RECT -0.1XX -0.1XX 0.1XX 0.1XX ;
        SPACING 0.4XX BY 0.4XX ;
END MYVIA1_ARRAY
```

### Side Notes: 
* Not all VIARULE GENERATE have this issue. 
* single cut vias don't have this issue.
* I have observed that the conversion of signals names **that are non-scalars** on text layers (pin labels) have some issues, if the DEF name is `addr[2]` for example the text layer has `net123` and not the actual name. Not sure if this an actual issue or if it is for compatibility reasons. 

Best regards


Discussion / hints:

Could you please provide a minimal reproducible testcase ?
That would be a lef with at least 2 metal and one via layer and a def file with the VIA in question and a NET referencing this via.
The VIARULE's get interpreted by OpenRoad and the resulting VIA is written in the VIAS section of the def file.
Without data it's hard to tell whether this is an OpenRoad or an klayout issue.

Thank you for your swift reply. I admit sending a bug report without a minimum reproducible isn't the nicest thing and for that I apologize. Please refer to my openroad slack discussion link about why I *think* it's a def2gds/klayout issue, maybe I'm wrong. I may put together a minimum test case that I can share in the future, it's just I don't know how to do it right now. 

Best Regards,
I did read the slack discussion, it contains some more context, but not more data.

Since klayout/strm2oas just convert def to gds and the def via just contain rectangles, I don’t see how that can go wrong unless you are changing units, because there is no math involved.
Applying the via rules to generate the via needs some math in OpenRoad.
You claim other tools would convert the def correctly … which I find puzzling.

But here comes in a minimal testcase with just the via in question, it’s the reproducibility that matters.

lef/def are Text Formats and you can run klayout or it’s command line strm2gds buddy program to convert them to gds.

Creating a testcases is as easy as taking the def, start stripping away stuff you don’t need, rerun strm2oas to see whether it still runs, rinse and repeat. After a couple iterations, you will be able to reduce def and then lef to a few lines that are relevant.

The def header info (VERSION, DESIGN, DIEAREA), VIAS section with the one VIA in question and the NETS  section with one “- netname .. + coordinates vianame ;”  and finally ENDDESIGN.

Don’t worry about the proprietary nature of lef, the size of a via hole in certain technologies and its metal overhang are not exactly secrets. But with a couple of lines testcase, you can easily change layer/via names and the lef via size and still have your testcase fail.

Last but not least, you can get in touch with Matthias directly and not make testcase public.
@rr667 That's funny, because VIARULE is not considered at all from LEF. The workaround might be an indirect effect - e.g. a rounding issue. This is what @stefanottili also mentioned.

KLayout reads VIA definitions, but not VIARULE. AFAIK VIARULE should be read by the router to generate VIA statements. @stefanottili Please correct me if I am wrong.

A snippet from the DEF plus the corresponding VIA definition is needed.

A side note: version 0.28.5 (or 0.28.8) is somewhat outdated.

Matthias
I don’t see how this could be a klayout bug from your description.
Please provide a test case or close this issue.
Hi @rr667 

Any updates?

Matthias
Hello @klayoutmatthias 

Yes, I have followed @stefanottili  excellent instructions to make the reproducible, here's what I have done: 

* I have created a floorplan with some design to have the power stripes laid out. I exported the `.def` with `write_def` command in OR.
* I continued to strip down the `.def` piece by piece while running `def2gds` script to make sure it compiles. 
* I ended up with the via-rule-generate cell connected to a net.  _The GDS has the DRC issue unfortunately._ 
* I learnt more about LEF/DEF and created an isolated different example that I can share publicly. _The problem persists with my example._ 

* @stefanottili  is right, Klayout operates on the DEF not the TECH LEF. OpenROAD is what produces the DEF according to the VIARULE GENERATE algorithm it has. 

Here's the reproducible (there's a shell in there to run def2gds python script I copied from ORFS, the outputs are there in case you don't want to run it).

[via_issue_ob.tar.gz](https://github.com/user-attachments/files/17349389/via_issue_ob.tar.gz)

@stefanottili Thank you again for being patient, please let me know if something is incomplete. I couldn't read in the def back after modifying it and view it with OpenROAD, is this necessary? 

Here's my observations:

* The workaround works because there's something about how (KLayout?) interprets the DEF for some numbers, as @klayoutmatthias has pointed out, a possible rounding error. I could be wrong.
* The issue is exactly on the right side of the via-rule-generate cell, the left side is fine. Increasing the overhangs by `0.001` adds extra `0.001` to the left side but the right side, that used to give DRC errors, is fixed!

![image](https://github.com/user-attachments/assets/3e185090-777a-4d25-b8bc-cf4e1fb5127a)

![image](https://github.com/user-attachments/assets/a05d0e48-1109-4c80-91f0-dd2576a208a0)

In OpenROAD (DEF viewer?) the right side is fine: 

![image](https://github.com/user-attachments/assets/b33a8235-a2e6-4fa7-97c6-683c2b5ab620)

## DEF 

```
VERSION 5.8 ;
DIVIDERCHAR "/" ;
BUSBITCHARS "[]" ;
DESIGN dummy_design ;
UNITS DISTANCE MICRONS 1000 ;
 
VIAS 1 ;
    - via1_2_XXXX_XXX_X_X_XXX_XXX + VIARULE MYVIA1_ARRAY + CUTSIZE 246 246  + LAYERS METAL_1 VIA_1 METAL_2  + CUTSPACING 231 231  + ENCLOSURE 62 137 8 61  + ROWCOL 1 8  ;
END VIAS
 
 
SPECIALNETS 1 ;
    - VDD ( PIN VDD ) ( * vdd ) + USE POWER
      + ROUTED METAL_1 0 + SHAPE STRIPE ( 23620 7840 ) via1_2_XXXX_XXX_X_X_XXX_XXX ;
END SPECIALNETS
 
END DESIGN
```

## TECH LEF

```
VERSION 5.6 ;
BUSBITCHARS "[]" ;
DIVIDERCHAR "/" ;
 
UNITS
  DATABASE MICRONS 1000 ;
END UNITS
MANUFACTURINGGRID 0.001 ;
# Original:  (commented)
# DRC ERROR ON THE RIGHT SIDE OF VIA ARRAY
 
# VIARULE MYVIA1_ARRAY GENERATE
#     LAYER METAL_1 ;
#         ENCLOSURE 0.061 0.007 ;
#     LAYER METAL_2 ;
#         ENCLOSURE 0.061 0.007 ;
#     LAYER VIA_1 ;
#         RECT -0.123 -0.123 0.123 0.123 ;
#         SPACING 0.463 BY 0.463 ;        
# END MYVIA1_ARRAY
 
 
# Edit: Increased overhangs as a workaround 
# Left side has 0.001 extra 
# Right side is just right (no DRCs)
 
VIARULE MYVIA1_ARRAY GENERATE
    LAYER METAL_1 ;
        ENCLOSURE 0.062 0.007 ; # Inc. overhang1 by 0.001
    LAYER METAL_2 ;
        ENCLOSURE 0.061 0.008 ; # Inc. overhang2 by 0.001
    LAYER VIA_1 ;
        RECT -0.123 -0.123 0.123 0.123 ; # DEF CUTSIZE 246 246
        SPACING 0.463 BY 0.463 ;         # DEF CUTSPACING 231 231
END MYVIA1_ARRAY
 
# DEF ENCLOSURE 62 137 8 61
END LIBRARY

```
@rr667 Thanks for the test case, it pinpoints the issue.
If you make the following changes to your testcase lef/def, you can load it in openroad

lef: you have to define the 3 layers (2 routing, 1 via), the WIDTH only matters for default NETS, it's value is irrelevant here, but some tools need it to load lef.
```
LAYER METAL_1
  TYPE ROUTING ;
  DIRECTION HORIZONTAL ;
    WIDTH 0.6 ;
END METAL_1

LAYER VIA_1
  TYPE CUT ;
END VIA_1

LAYER METAL_2
  TYPE ROUTING ;
  DIRECTION VERTICAL ;
    WIDTH 0.6 ;
END METAL_2
```

The def requires DIEAREA and your SPECIALNET VDD's connectivity section references a PIN VDD that you've not defined, just comment it out.
```
DIEAREA ( 21500 7000 ) ( 26000 9000 ) ;
...
SPECIALNETS 1 ;
    - VDD
# ( PIN VDD ) ( * vdd )
      + USE POWER
      + ROUTED METAL_1 0 + SHAPE STRIPE ( 23620 7840 ) via1_2_XXXX_XXX_X_X_XXX_XXX ;
END SPECIALNETS
```
if you create a file called or_via_fix.or you can run
openroad -gui -or_via_fix.or
but you have to "zoom in" to see the via due to OR's size based draw filtering (OR feature/bug).
```
read_lef klayout_tech.lef
read_def or_via_fix.def
```
The following command will use klayout's buddy tool strm2oas to convert lef/def/map to oas.
Your def2gds.sh works too, but it maps the metal/via layer twice, I'll leave it to Matthias to explain that.
I prefer to use .map files instead of .lyt

strm2oas can do what def2stream.py does using klayout, except adding a possible sealring reference.
```
        strm2oas \
        --lefdef-no-implicit-lef \
        --lefdef-lefs klayout_tech.lef \
        --lefdef-map or_via_fix.map \
        or_via_fix.def \
        or_via_fix.strm2oas.oas
```
or_via_fix.map
```
METAL_1 VIA 1 0
METAL_2 VIA 2 0
VIA_1 VIA 3 0
```

And now to the issue at hand:
@klayoutmatthias I believe it's a klayout bug, because it create too small of an overhang.
But it's caused by ambiguity in the VIARULE, that leads to a rounding issue no matter what.

CUTSIZE 246 246  + CUTSPACING 231 231  + ENCLOSURE 62 137 8 61 + ROWCOL 1 8

246\*8 + 231\*7 + 8*2 = 19585

The via metal has to be centered, but the width is not even, this is ambiguous.

klayout currently calculates the metal left/right coordinates as -19585/2,+19585/2.
A fix would be to calculate the left coordinate as -19585/2 and then add 19585 to get the right coordinate.
But even that is ambiguous due to the left coordinate calculation dividing an uneven number by two.

The real fix is to have an even SPACING in the VIARULE. That way klayout would work "as is".

Even the comment in the lef acknowledges that 463 leads to an ambiguous 231(.5) ...
```
SPACING 0.463 BY 0.463 ;         # DEF CUTSPACING 231 231
```

You figured it out really quickly ⚡
Thank you for looking into this, I will try the OR read def later 👍 

Please change the tech lef SPACING to 0.464, redo your floorplan and let us know whether the drc goes away.
Perfect. I can reproduce the issue.

I think it is possible to change the rounding in a way the DRC constraints are not compromised. Still - as Stefan pointed out - the results are slightly arbitrary as the via's anchor point is not well defined.

Thanks for this great debugging effort!

Matthias

Fix patch:
diff --git a/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.cc b/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.cc
index ec404f7c8..a8dac54e5 100644
--- a/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.cc
+++ b/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.cc
@@ -223,8 +223,13 @@ RuleBasedViaGenerator::create_cell (LEFDEFReaderState &reader, Layout &layout, d
 
   //  NOTE: missing cuts due to pattern holes don't change mask assignment
 
-  db::Vector vs ((m_cutsize.x () * m_columns + m_cutspacing.x () * (m_columns - 1)) / 2, (m_cutsize.y () * m_rows + m_cutspacing.y () * (m_rows - 1)) / 2);
-  db::Box via_box (m_offset - vs, m_offset + vs);
+  //  special rounding to ensure the dimensions are correct for non-even width or height (issue #1877)
+  db::Vector vs ((m_cutsize.x () * m_columns + m_cutspacing.x () * (m_columns - 1)), (m_cutsize.y () * m_rows + m_cutspacing.y () * (m_rows - 1)));
+  if (vs.x () % 2 != 0 || vs.y () % 2 != 0) {
+    reader.warn (tl::sprintf (tl::to_string (tr ("Via has odd width or height (x,y dimension of cut array is %s database units) - this may lead to inaccurate positioning of the via")), vs.to_string ()));
+  }
+  db::Point via_ll = m_offset - db::Vector (vs.x () / 2, vs.y () / 2);
+  db::Box via_box (via_ll, via_ll + vs);
 
   std::set <unsigned int> dl;
 
@@ -975,7 +980,7 @@ LEFDEFReaderState::~LEFDEFReaderState ()
 }
 
 void
-LEFDEFReaderState::common_reader_error (const std::string &msg)
+LEFDEFReaderState::error (const std::string &msg)
 {
   if (mp_importer) {
     mp_importer->error (msg);
@@ -983,7 +988,7 @@ LEFDEFReaderState::common_reader_error (const std::string &msg)
 }
 
 void
-LEFDEFReaderState::common_reader_warn (const std::string &msg, int warn_level)
+LEFDEFReaderState::warn (const std::string &msg, int warn_level)
 {
   if (mp_importer) {
     mp_importer->warn (msg, warn_level);
@@ -1117,7 +1122,7 @@ LEFDEFReaderState::read_single_map_file (const std::string &path, std::map<std::
       size_t max_purpose_str = 15;
 
       if (! ex.try_read_word (w1) || ! ex.try_read_word (w2, "._$,/:") || ! try_read_layers (ex, layers) || ! try_read_layers (ex, datatypes)) {
-        common_reader_warn (tl::sprintf (tl::to_string (tr ("Reading layer map file %s, line %d not understood - skipped")), path, ts.line_number ()));
+        warn (tl::sprintf (tl::to_string (tr ("Reading layer map file %s, line %d not understood - skipped")), path, ts.line_number ()));
         continue;
       }
 
@@ -1143,7 +1148,7 @@ LEFDEFReaderState::read_single_map_file (const std::string &path, std::map<std::
           name = "REGIONS_NONE";
           lp = RegionsNone;
         } else if (w2 != "ALL") {
-          common_reader_warn (tl::sprintf (tl::to_string (tr ("Reading layer map file %s, line %d - ignoring unknowns REGION purpose %s (use FENCE, GUIDE or ALL)")), path, ts.line_number (), w2));
+          warn (tl::sprintf (tl::to_string (tr ("Reading layer map file %s, line %d - ignoring unknowns REGION purpose %s (use FENCE, GUIDE or ALL)")), path, ts.line_number (), w2));
         }
 
         for (std::vector<int>::const_iterator l = layers.begin (); l != layers.end (); ++l) {
@@ -1176,7 +1181,7 @@ LEFDEFReaderState::read_single_map_file (const std::string &path, std::map<std::
 
           if (*p == "DIEAREA" || *p == "ALL" || *p == "COMP") {
 
-            common_reader_warn (tl::sprintf (tl::to_string (tr ("Reading layer map file %s, line %d: NAME record ignored for entity: %s")), path, ts.line_number (), *p));
+            warn (tl::sprintf (tl::to_string (tr ("Reading layer map file %s, line %d: NAME record ignored for entity: %s")), path, ts.line_number (), *p));
 
           } else {
 
@@ -1193,7 +1198,7 @@ LEFDEFReaderState::read_single_map_file (const std::string &path, std::map<std::
               if (label_purpose == Pins || label_purpose == LEFPins) {
                 layer_defs.push_back (std::make_pair (lp.front (), label_purpose == Pins ? Label : LEFLabel));
               } else {
-                common_reader_warn (tl::sprintf (tl::to_string (tr ("Reading layer map file %s, line %d: NAME record ignored for purpose: %s")), path, ts.line_number (), purpose_to_name (label_purpose)));
+                warn (tl::sprintf (tl::to_string (tr ("Reading layer map file %s, line %d: NAME record ignored for purpose: %s")), path, ts.line_number (), purpose_to_name (label_purpose)));
               }
 
             } else {
@@ -1226,7 +1231,7 @@ LEFDEFReaderState::read_single_map_file (const std::string &path, std::map<std::
       } else if (w1 == "COMP") {
 
         //  ignore "COMP (ALL) ..."
-        common_reader_warn (tl::sprintf (tl::to_string (tr ("Reading layer map file %s, line %d: COMP entry ignored")), path, ts.line_number ()));
+        warn (tl::sprintf (tl::to_string (tr ("Reading layer map file %s, line %d: COMP entry ignored")), path, ts.line_number ()));
 
       } else {
 
@@ -1261,7 +1266,7 @@ LEFDEFReaderState::read_single_map_file (const std::string &path, std::map<std::
               if (ex.test (":VOLTAGE:")) {
                 double f = 0.0;
                 ex.read (f);
-                common_reader_warn (tl::sprintf (tl::to_string (tr ("Reading layer map file %s, line %d: NET voltage constraint ignored for layer %s")), path, ts.line_number (), w1));
+                warn (tl::sprintf (tl::to_string (tr ("Reading layer map file %s, line %d: NET voltage constraint ignored for layer %s")), path, ts.line_number (), w1));
               }
 
             } else if (i->second == ViaGeometry) {
@@ -1284,7 +1289,7 @@ LEFDEFReaderState::read_single_map_file (const std::string &path, std::map<std::
 
           if (i == purpose_translation.end ()) {
 
-            common_reader_warn (tl::sprintf (tl::to_string (tr ("Reading layer map file %s, line %d: purpose %s ignored for layer %s")), path, ts.line_number (), ps, w1));
+            warn (tl::sprintf (tl::to_string (tr ("Reading layer map file %s, line %d: purpose %s ignored for layer %s")), path, ts.line_number (), ps, w1));
 
           } else if (i->second == All) {
 
@@ -1379,7 +1384,7 @@ LEFDEFReaderState::open_layer (db::Layout &layout, const std::string &n, LayerPu
         msg += tl::to_string (tr (" Via size ")) + via_size.to_string ();
       }
 #endif
-      common_reader_warn (msg + tl::to_string (tr (" - layer is ignored")));
+      warn (msg + tl::to_string (tr (" - layer is ignored")));
     }
 
     return ll;
diff --git a/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.h b/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.h
index 647e3afd2..9720d6edf 100644
--- a/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.h
+++ b/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.h
@@ -1354,9 +1354,19 @@ class DB_PLUGIN_PUBLIC LEFDEFReaderState
     return m_foreign_cells;
   }
 
+  /**
+   *  @brief Issues an error
+   */
+  void error (const std::string &msg);
+
+  /**
+   *  @brief Issues a warning
+   */
+  void warn (const std::string &msg, int warn_level = 1);
+
 protected:
-  virtual void common_reader_error (const std::string &msg);
-  virtual void common_reader_warn (const std::string &msg, int warn_level = 1);
+  virtual void common_reader_error (const std::string &msg) { error (msg); }
+  virtual void common_reader_warn (const std::string &msg, int warn_level) { warn (msg, warn_level); }
 
 private:
   /**


Test patch:
diff --git a/src/plugins/streamers/lefdef/unit_tests/dbLEFDEFImportTests.cc b/src/plugins/streamers/lefdef/unit_tests/dbLEFDEFImportTests.cc
index 6c1127863..fd890f96b 100644
--- a/src/plugins/streamers/lefdef/unit_tests/dbLEFDEFImportTests.cc
+++ b/src/plugins/streamers/lefdef/unit_tests/dbLEFDEFImportTests.cc
@@ -1077,3 +1077,26 @@ TEST(213_no_duplicate_LEF)
   db::compare_layouts (_this, ly, fn_path + "au.oas", db::WriteOAS);
 }
 
+//  issue-1877 (VIA placement rounding)
+TEST(214_issue1877)
+{
+  db::Layout ly;
+
+  std::string fn_path (tl::testdata ());
+  fn_path += "/lefdef/issue-1877/";
+
+  db::LEFDEFReaderOptions lefdef_opt = default_options ();
+  lefdef_opt.set_map_file ("tech.map");
+  lefdef_opt.set_read_lef_with_def (true);
+  db::LoadLayoutOptions opt;
+  opt.set_options (lefdef_opt);
+
+  {
+    tl::InputStream is (fn_path + "test.def");
+    db::Reader reader (is);
+    reader.read (ly, opt);
+  }
+
+  db::compare_layouts (_this, ly, fn_path + "au.oas", db::WriteOAS);
+}
+
diff --git a/testdata/lefdef/issue-1877/au.oas b/testdata/lefdef/issue-1877/au.oas
new file mode 100644
index 000000000..3cf8767c5
Binary files /dev/null and b/testdata/lefdef/issue-1877/au.oas differ
diff --git a/testdata/lefdef/issue-1877/tech.lef b/testdata/lefdef/issue-1877/tech.lef
new file mode 100644
index 000000000..e96c48b57
--- /dev/null
+++ b/testdata/lefdef/issue-1877/tech.lef
@@ -0,0 +1,27 @@
+VERSION 5.6 ;
+BUSBITCHARS "[]" ;
+DIVIDERCHAR "/" ;
+ 
+UNITS
+  DATABASE MICRONS 1000 ;
+END UNITS
+MANUFACTURINGGRID 0.001 ;
+ 
+LAYER METAL_1
+  TYPE ROUTING ;
+  DIRECTION HORIZONTAL ;
+    WIDTH 0.6 ;
+END METAL_1
+
+LAYER VIA_1
+  TYPE CUT ;
+END VIA_1
+
+LAYER METAL_2
+  TYPE ROUTING ;
+  DIRECTION VERTICAL ;
+    WIDTH 0.6 ;
+END METAL_2 
+
+END LIBRARY
+
diff --git a/testdata/lefdef/issue-1877/tech.map b/testdata/lefdef/issue-1877/tech.map
new file mode 100644
index 000000000..6d3fd1898
--- /dev/null
+++ b/testdata/lefdef/issue-1877/tech.map
@@ -0,0 +1,4 @@
+DIEAREA ALL 1 0
+METAL_1 ALL 2 0
+VIA_1 ALL 3 0
+METAL_2 ALL 4 0
diff --git a/testdata/lefdef/issue-1877/test.def b/testdata/lefdef/issue-1877/test.def
new file mode 100644
index 000000000..cf273a04c
--- /dev/null
+++ b/testdata/lefdef/issue-1877/test.def
@@ -0,0 +1,21 @@
+VERSION 5.8 ;
+DIVIDERCHAR "/" ;
+BUSBITCHARS "[]" ;
+DESIGN dummy_design ;
+UNITS DISTANCE MICRONS 1000 ;
+
+DIEAREA ( 21500 7000 ) ( 26000 9000 ) ;
+
+VIAS 1 ;
+    - via1_2_XXXX_XXX_X_X_XXX_XXX + VIARULE MYVIA1_ARRAY + CUTSIZE 246 246  + LAYERS METAL_1 VIA_1 METAL_2  + CUTSPACING 231 231  + ENCLOSURE 62 137 8 61  + ROWCOL 1 8  ;
+END VIAS
+ 
+ 
+SPECIALNETS 1 ;
+    - VDD 
+# ( PIN VDD ) ( * vdd ) 
+      + USE POWER
+      + ROUTED METAL_1 0 + SHAPE STRIPE ( 23620 7840 ) via1_2_XXXX_XXX_X_X_XXX_XXX ;
+END SPECIALNETS
+ 
+END DESIGN