Large difference in measurements of area of same/identical polygons

Hi,

I'm working on a very simple DRC script with the goal to have a very basic report of area usage in a layout. In the end, it should report the percentage of the die being used for resistors, transistors, capacitors, routing, and more.

The code I have so far reports the measured are to the terminal. Since I saw strange values being reported, I broke down the problem, and this is what I do, and what gives odd results. Hopefully someone spots my mistake.

I have three pieces of code, which can be run after each other on a GDS database. It has to be run in the same order as whown below.

1) Code to measure all metal capacitors:

metal1 = input(46)
metal2 = input(48)
metal3 = input(50)
metal4 = input(52)
metal5 = input(54)

metal1s = metal1 - metal1.sized(-0.05).sized(0.05)
metal2s = metal2 - metal2.sized(-0.05).sized(0.05)
metal3s = metal3 - metal3.sized(-0.05).sized(0.05)
metal4s = metal4 - metal4.sized(-0.05).sized(0.05)
metal5s = metal5 - metal5.sized(-0.05).sized(0.05)

momcapM1M2 = metal1s & metal2s
momcapM2M3 = metal2s & metal3s
momcapM3M4 = metal3s & metal4s
momcapM4M5 = metal4s & metal5s
momcapM1M3 = momcapM1M2 & momcapM2M3
momcapM2M4 = momcapM2M3 & momcapM3M4
momcapM3M5 = momcapM3M4 & momcapM4M5
momcapM1M4 = momcapM1M2 & momcapM3M4
momcapM2M5 = momcapM2M3 & momcapM4M5
momcapM1M5 = momcapM1M4 & momcapM2M5

momcapTotal = momcapM1M2 + momcapM1M3 + momcapM1M4 + momcapM1M5 + momcapM2M3 + momcapM2M4 + momcapM2M5 + momcapM3M4 + momcapM3M5 + momcapM4M5

momcapTotal.sized(0.05).merge(0).with_area(2.5.micron2, nil).output(101,1)

log("Total Area momcapTotal: #{momcapTotal.merge(0).area}")

2) Code to measure mos capacitors:

poly1 = input(41)
diffusion = input(1)
gate = poly1 & diffusion
gate.with_area(5.micron2, nil).merge(0).output(100,1)
log("Total Area all gate decoupling: #{gate.merge(0).area}")

3) Code to define area where metal and mos capacitors are on top of each other (just the part of the code that shows the problem):

MOS = input(100, 1).merge(0)
MOM = input(101, 1).merge(0)
log("Total MOM: #{MOM.merge(0).area}")
log("Total MOS: #{MOS.merge(0).area}")

In scripts 1 and 2 I generate some new layers (100 and 101), which are used in script 3.

The output of those scripts if run after each other:

Total Area momcapTotal: 6690.6898
Total Area all gate decoupling: 16516.790999999997
Total MOM: 13285.833999999999
Total MOS: 14189.795199999999

The problem:
The values for output lines 1 and 3 should be the same. This is not the case.
The values for output lines 2 and 4 should be the same. This is not the case.
Since the output is not what I expect, I must be doing something wrong.

Can anyone spot the problem? I have been looking at this code for too long to see it.

I have added the code files and a test-layout to test it for yourself.

Many thanks in advance.

With kind regards,

Sjoerd

Comments

  • Hi sheppie,

    I hope I understood the problem correctly. To me this boils down to these lines:

    momcapTotal.sized(0.05).merge(0).with_area(2.5.micron2, nil).output(101,1)
    log("Total Area momcapTotal: #{momcapTotal.merge(0).area}")
    ...
    MOM = input(101, 1).merge(0)
    log("Total MOM: #{MOM.merge(0).area}")
    

    So as I assume "input(101, 1)" will read the layer that was written with "output(101,1)" your expectation is that the area will be the same, right?

    If that true, then the problem is about the difference between "in-place" and "out-of-place" version of methods. Here is an example:

    layer = ... # a layer (e.g. from input(...))
    
    # in-place version: will modify the layer
    layer.size(0.05)
    
    # out-of-place version: will create a new, sized layer and not touch "layer"
    sized_layout = layer.sized(0.05)
    

    So in the line

    momcapTotal.sized(0.05).merge(0).with_area(2.5.micron2, nil).output(101,1)
    

    "sized" will produce a sized copy and not modify "momcapTotal". "merge(0)" again is an in-place version (the out-of-place version is called "merged"), but will act on the sized copy. "with_area" again is an out-of-place version again.

    So in the end you produced a layer derived from "momcapTotal" and wrote this to the output, but you did not modify "momcapTotal". So when you compute it's area, the layer is not the one you have written.

    "merge(0)" is essentially redundant, as this is implied by all operations. So if your intention is to work with the modified version of "momcapTotal", the lines should be:

    momcapTotalModified = momcapTotal.sized(0.05).with_area(2.5.micron2, nil)
    momcapTotalModified.output(101,1)
    log("Total Area momcapTotalModified: #{momcapTotalModified.area}")
    ...
    MOM = input(101, 1)
    log("Total MOM: #{MOM.area}")
    

    With these changes, the results should be identical.

    The same scheme applies to the MOS area and gate layer.

    Matthias

  • edited September 2019

    Hi Matthias,

    Thank you for your very detailed reply. I'll test your solution today, and get back with results.

    Your assumption is right when you state "So as I assume "input(101, 1)" will read the layer that was written with "output(101,1)" your expectation is that the area will be the same, right?". I expect it to be the same. Moreover, when just looking at the layer, and toggling the visibility, it looked the same too.

    There's definitely something to learn here for me, since I just started writing code for KLayout DRC. And the "in-place" and "out-of-place" is relatively new to me. Previously I wrote code for Calibre, and I do not recall that there (with Calibre) was such a difference.

    With kind regards,

    Sjoerd

  • Hi Matthias,
    I've updated the code according to your suggestions and it works perfectly now.
    Thank you for your support.
    With kind regards,
    Sjoerd

  • Not pertaining to the subject per se, but interesting to see we have a Calibre expert lurking - getting klayout to understand a "golden Caliber tape-in deck" (as many or most of today's foundries insist to pass) would be kinda Holy Grail-ish.

    I've never seen a Caliber runset but seems to me that a translator utility is not outside of the possible (not that I could, but "somebody"...).

    Food for thought.

  • Hi Jim,

    that's unfortunately a legal issue. Calibre's ruledecks are written in SVRF which is a format copyrighted by Mentor. It's strictly protected, so parsing it from open source tools is not possible.

    I wonder if any foundry might want to provide a KLayout version of their DRC. If they would like to, I'd assist. I just can't sign a NDA with them.

    Matthias

  • This is actually on my plate, and I have not much hope of getting it done on time - creating a full foundry PDK for klayout (fixed geometry transistors, and SPICE models, are in hand; DRC, extract, LVS require translation from Tanner (ca 2010) format. But the foundry is pretty jealous of its 80s-vintage technology and wants NDAs. They also might want to control distribution of the kit, as just about everyone does.

    I'm not sure that anything prevents anyone reading a Caliber deck and figuring out how to do what they do, other than any documents you had to sign to get it in the first place. You would not be creating SVRF code, but equivalent functionality in Ruby or whatever. Not so?

    Would you consider a collection of

    Caliber: {width check example}
    klayout: {width check example}

    Caliber: {enclosure example}
    klayout: {enclosure example}

    ...

    translations, to fall under Mentor's copyright? I'd call it fair use.

    And then maybe somebody takes the next step and makes a perl, python, sed (whatever) script that rearranges arguments, badda-bing? One DRC check-class at a time....

  • I'm just saying, an automated translation script would not be possible without violating copyright rules.

    A document describing the translation - well, I'd not do it myself, but I don't think this is forbidden.

    Regards,

    Matthias

Sign In or Register to comment.