Length values in expressions using TilingProcessor

edited September 18 in Ruby Scripting

Hello, I have a question regarding the TilingProcessor and expressions.

I have the following (shortened) setup:

tp = RBA::TilingProcessor::new
tp.frame = chip
tp.dbu = ly.dbu
tp.threads = threads
tp.tile_size(tile_size, tile_size)

tp.input("COMP", ly, top_cell.cell_index, COMP)
tp.input("Poly2", ly, top_cell.cell_index, Poly2)
tp.input("PMNDMY", ly, top_cell.cell_index, PMNDMY)

tp.var("space_to_COMP", 3.5 / ly.dbu)
tp.var("space_to_Poly2", 1.5 / ly.dbu)

tp.output("to_fill", TilingOperator::new(ly, top_cell, fill_cell.cell_index, fc_box_in_dbu, row_step_in_dbu, column_step_in_dbu, fc_origin_in_dbu))

tp.queue("
var COMP_20um_spacing = COMP.sized(20um).sized(-20um)
var fill_region = _tile & _frame - COMP_20um_spacing.sized(space_to_COMP) - Poly2.sized(space_to_Poly2) - PMNDMY;
_output(to_fill, fill_region)")

I get the following error:

ERROR: Worker thread: Length or area value with unit requires a layout context at line 3, position 36 (..20um).sized(-20um)

Which makes a lot of sense, however since the tiling processor has information about the dbu, shouldn't it be possible to convert the length value?
Or is there another way to supply the layout context?

As a workaround I can supply the constant as a variable like I did for the spacing:

tp.var("um20", 20 / ly.dbu)

Let me know if you need a full reproducible.

Thanks!

Comments

  • Hi Leo,

    You're correct, the layout context is not there and it's not just the DBU, with a layout context for example you can obtain layer indexes using angle brackets (e.g. "<2/0>" turns into the layer index). Bottom line is: a "layout context" is really a layout and although the tiling process gets the DBU, the layout is a not readily accessible.

    My recommendation was to turn the "20 µm" value into a variable like you suggested. I feel it's a good practice to define every physical value somewhere globally, so I would write:

    min_dim = 40
    
    ...
    tp.var("min_dim", min_dim / ly.dbu)
    ...
    
    tp.queue("
    var COMP_cleaned = COMP.sized(min_dim / 2).sized(-min_dim / 2)
    ...
    )
    

    (this is how I understand the intention of the code).

    BTW: in case you're trying filling, have you seen this: https://www.klayout.de/forum/discussion/2620/fill-multiorigin-and-manufacturing-grid#latest ?

    (and please use static origin, I am not happy with the auto_origin or multi_origin feature. Needs work :( )

    Best regards,

    Matthias

  • Hi Matthias,

    Thank you for the explanation! I will go with the variables :)

    No, I haven't seen the latest update yet. Very cool!

    Yes, I'm actually working on fill, but not for ihp-sg13g2 ;)
    This is for gf180mcu, where the fill rules are much simpler. It's just a fixed shape with a fixed stagger.

    However, I'm really looking forward to all the fill improvements! Having a reliable fill method for ihp-sg13g2, even for dense digital designs, would be absolutely great 🙌

    One more question: Have you thought about variable-size fill shapes? Magic allows to specify a minimum and maximum length, and will then try to fit the largest fill shapes.
    It is quite useful to get the to the last missing percent of density. However, I'm not sure how valuable this feature really is: whether modern process nodes allow arbitrary fill shapes, and whether it' actually needed (maybe not all processes are as picky as ihp-sg13g2).

    Best,
    Leo

  • Hi Leo,

    The way I know variable fill is implemented is by placing some grating pattern over your layout, the subtract the non-fill regions with some sizing and then clean up the cut grating parts with a under/oversize to remove the pieces where the grating a partially clipped. Finally the lines could be cut into pieces with a maximum length using a polygon processor.

    That should not be too difficult to implement, but from perspective of process isotropy and parasitic coupling, I think that the square fill pattern is easier to handle. I have seen line fill in advances nodes with a pronounced isotropic lithographic setup (dipole illumination) and multi-patterning. I think however, in these cases it's more common to include fill pattern in form of dummy gates for example.

    Matthias

  • Thank you for all the information, Matthias!

    Anyways, for now a simple fill is sufficient on gf180mcu :)

    I have one more question about the filler generation:

    I'm using sized to ensure the minimum spacing between the dummy comp and the other layers.
    However, for euclidean measurements the spacing (3.5um) is violated, as you can see:

    (The filler cell is the red one.)

    I don't quite understand why this is happening.
    I could pass a different mode to sized, but this should only change how corners are handled.
    Does fill_region somehow need to use euclidian measurements?

    Leo

  • Hi Leo,

    Basically the sizing implements an edge shift, so provided, the filler (red shape I assume) is outside the sized region, it should observe the Euclidian distance. I suspect the latter is not the case. First thing I'd try is to confirm that the fill shapes are inside the allowed area by dumping the sized layer. If that is not the case, then there is a problem with the fill shape definition I assume. Maybe you can share the current script, so I can take a look.

    I also like to use this opportunity to advertise some new features of KLayout 0.30.4 - namely "margin" for the fill pattern (helps keeping a distance without need for "sized") and "fill_exclude" ("fill" method) which avoids having to do the boolean NOT for computing the area to fill.

    Best regards,

    Matthias

  • Hi Matthias,

    As this is a Ruby script that uses the TileOutputReceiver, I'm not sure how easy it is to dump the sized layers.

    Perhaps you can take a quick look: https://github.com/wafer-space/gf180mcu/tree/ed1a3104ffe3dc387276e1bc9f031f6f5071092b/gf180mcuD/libs.tech/klayout/tech/drc/filler_generation

    But maybe don't put too much time into this. I'll convert this to a DRC script soon, like you have done for Greyhound, as this seems to be the better way to do fill.

    fill_exclude sounds great! However, I don't think I can use margin on gf180mcu, since I need to keep different distances to different layers.
    See the Dummy COMP rules for example: https://gf180mcu-pdk.readthedocs.io/en/latest/physical_verification/design_manual/drm_13_1.html

    Thanks a lot for your help!
    Leo

  • Hi Leo,

    that actually nice and clean code :)

    It's smart to do the computation inside the tiles - that saves memory and scales well on multiple cores and I like the idea of the TilingOperator. DRC will do something similar, but still collect the intermediate results in full layers.

    I don't see an obvious flaw with the sizing function and why the Euclidian distance should be violated. That needs more debugging. As of now my only comment is about the tile border: the border is the overlap between the tiles - so it should be sufficient to specify the maximum required search range. In your case that is equivalent to the maximum sizing value applied (in µm). For COMP I think that is 10µm. I see you used "tile_size + 30" which I guess is much too big, unless you have very small tiles. But that should not change the results, it's rather an optimization.

    To dump the internals, you need to create a second output channel and use "_output" in the tiling processor script. Debugging DRC is easier as you just need to dump a layer.

    The "fill_exclude" should make life a little easier and avoid generating complex polygons with many holes. STI is a complex case, I agree. So it's clear you can't use "fill_margin" in that case. It's okay not to use it - just try to avoid very large sizing operations. They tend to create very complex overlap situations which are time consuming to resolve. Sometimes it is much more efficient to do a large sizing in multiple steps, like 20µm in four steps of 5µm each.

    Best regards,

    Matthias

  • Oh wow, thank you! I'm basically just trying to get things working 😄

    I thought that tiled mode in DRC would behave the same. Why does DRC collect the intermediate results in full layers?
    In your script, I can see that you perform layer operations in deep mode, and fill in tiled mode:

    # Preparation is done in deep mode
    deep 
    
    to_fill = chip
    metal1_fill = input(*metal1_fill_layer)
    fill_excl = metal1 + metal1_fill + metal1_nofill
    
    # Fill can be done in tiled mode
    tiles(tile_size)
    tile_borders(0.0)
    threads(ncpu)
    ...
    

    Is it not possible to do preparations in tiled mode at all? Or did you simply choose not to?


    About the tile border: I chose 30um because of rule DCF.7a (space to scribe line > 26um).
    In theory, I would have a marker layer for the scribe line when filling a whole reticle. In practice, however, I'm filling just the user project, and the scribe line is implied as outside of it, so I used _frame as boundary. Therefore, I think you're right and the margin could be reduced to 10um.

    However, just for my understanding: Let's say I need to ensure a spacing of 26um between layer 1 and layer 2. Would I then need a tile margin of > 26um? Because some shapes of layer 1 could be right at the border, and therefore we also need to fetch shapes of layer 2 that are > 26um outside the border?


    Thank you for the tip about adding more output channels. This allowed me to debug the issue, and I noticed something that I did not expect. (I'm probably just using the DRC expressions incorrectly).

    I think my problem comes down to: COMP.sized(um10).sized(-um10)
    With this operation I wanted to close notches and holes of 20um size (DCF.1a).
    My assumption was that by performing first a positive and then a negative sized with the same magnitude, I would get the same general outline for shapes (at least not smaller).

    But, as it turns out, the outline actually shrinks at the padring corners. This is why the spacing between COMP and Dummy COMP is violated.

    For easier viewing, I attached the layout for the top right padring edge, with:

    0/1 is COMP.sized(um10)
    0/2 is COMP.sized(space_to_COMP)
    0/3 is COMP.sized(um10).sized(-um10)
    0/4 is COMP.sized(um10).sized(-um10).sized(space_to_COMP)

    It seems, that the staircase patterns grows more than the 45° shape, and therefore when executing sized(-um10) the outline is smaller than before.
    Do you have any suggestions on how to handle this case?


    Thanks for letting me know that sizing in multiple steps can be faster.
    Is this because small holes are closed after the first step and are not considered for subsequent steps?

    For example here:

    All the geometry of the standard cells will be merged after the first sizing step?
    Is it sufficient to supply the steps argument to sized? Or do I need to call e.g. sized(5um) four times?

    I'm new to all this DRC stuff, so thanks a lot for explaining it to me.

    Leo

  • Hi Leo,

    I thought that tiled mode in DRC would behave the same. Why does DRC collect the intermediate results in full layers?

    That is because DRC is "interactive". That means every operation is one step. So every operation runs in tiled mode, but the results are still expanded. To solve this, I would need to switch to "execution graph" (like other tools). That means, the operations are collected in an operation graph that is executed as a whole and can be put into a tiling processor as a long script.

    However, the disadvantage is that you can't make decisions depending on the data for example. I was hoping that deep mode is a good enough alternative.

    Regarding the tile border: I was referring to this line:

    Actually, the border is just the added dimension, so it can simply be:

    tp.tile_border(30, 30)
    

    if you want the search window to be 30 micron bigger than the actual tile size. So no need to add the tile size :)

    However, just for my understanding: Let's say I need to ensure a spacing of 26um between layer 1 and layer 2. Would I then need a tile margin of > 26um? Because some shapes of layer 1 could be right at the border, and therefore we also need to fetch shapes of layer 2 that are > 26um outside the border?

    Yes, you could use 26µm + 1 DBU, but I think 30µm is not big waste of resources, compared to adding the tile size :)

    The size problem is actually an interesting one.

    I created a test case. The green is my input, the yellow one is the input sized by 10µm:

    You see that the "sawtooth" extends beyond the diagonal. That is because the rectangular corners add 10µm on all sides which gives a maximum size along the diagonal of a little over 14µm is Euclidian distance.

    The same happens when you apply -10µm sizing, but this time in reverse. The result is the same thing you observed:

    So formally, the problem comes from a exaggeration of sizing (in both directions) at rectangular corners. A Euclidian size would formally generate the circle around those corners, but that is not commonly accepted size function interpretation.

    One solution is to apply the octagon limit to the size function which is closer to the Euclidian circle and avoids that exaggeration:

    I DRC you enable octagon limit by using:

    s1 = l1.sized(10.0, octagon_limit)
    

    For the Region class, the mode value ("size" or "sized" method) for octagon limit is 3.

    Another option I am using when you wish to stay on the conservative size (too much sizing is better than too little) is to apply x and y sizing separately. That gives a square hull and works nicely for closing gaps:

    The DRC code is this:

    s1 = l1.sized(10.0, 0.0).sized(0.0, 10.0)
    s2 = s1.sized(-10.0, 0.0).sized(0.0, -10.0)
    

    (I hope I did not forget to answer one of the many questions above :) )

    Matthias

  • Hi Matthias,

    sorry for my late reply, things got in the way.

    I can't believe I added the tile size to the tile border 😆
    Thanks a lot for pointing this out! By doing this, the effective tile area was about 9 times larger than necessary.
    After fixing this, the filler generation is muuuch faster and uses far less memory - who would have thought!


    Thanks also for explaining the difference between the interactive DRC mode and the TilingOperator in Ruby.
    I'll keep this in mind when deciding which method to use for which task.

    Having the ability to branch on the data is really useful, but I assume an "execution graph" has many benefits too.
    I imagine it would then even be possible to multithread parts of a DRC script even on a flat/deep layout, if you know where the data dependencies are?
    Currently, for IHP, the DRC script is split up into pieces, with each piece running in a separate KLayout process. A practical, but not necessary elegant solution.

    Is an "execution graph"-based DRC mode planned for KLayout at some point? I would imagine that is a massive undertaking?


    Thank you for looking into my sizing "problem". I was quite sure that it is not a bug, but rather the way how I use the sizing operation. Your explanation made everything clear :)

    I have now settled on the octagon_limit for the operations where "too much sizing is better than too little", and it works perfectly!

    Leo

    PS: See you at the webinar next week!

Sign In or Register to comment.