pcb-rnd knowledge pool
Courtyard: ko.* layer groups: under the hood
ko_id2 by Tibor 'Igor2' Palinkas on 2020-04-27
Tags: howto, drc5, keepout, courtyard, drc, drc_script
Abstract: The "courtyard" (ko.courtyard) feature is fully working mechanical keepout/courtyard with DRC checks provided by the default drc_query config. This node describes how the drc rule is implemented.
The DRC check for ko.* is implemented as a query script in the default config shipped with drc_query. You can find it in trunk/src_plugins/drc_query/drc_query.conf if you search for "ko_named".
When you run the drc, it will run all rules that are not disabled. Each rule is just a query script. Whatever the query script yields (matches to) is a drc violation.
Below I dissect our query script for the ko.* rule at the current version. I do this hoping that a few hardcore users are still following and want to learn how to write their own rules. This script is a moderate complexity one so it is a good example for this purpose. We have a query tutorial with simpler scripts available.
The query script is this:
rule courtyard let A (@.layer.purpose ~ "^ko.") thus @ let B A assert (A != B) && (A.IID <= B.IID) && (A.layer.position == B.layer.position) && (A.layer.purpose == B.layer.purpose) && (overlap(A, B)) \\ thus violation(DRCGRP1, A, DRCGRP2, B, DRCTEXT, "On keepout type ", DRCTEXT, A.layer.purpose, DRCTEXT, " ", DRCTEXT, A.layer.a.drc_desc)
The first line, 'rule' is just an announcement to the query parser so it knows a multi-instruction script follows.
the 'let A ...' line creates a list called 'A' (arbitrary name). In this list it looks at every object found on the board (@) and uses a regex match on the object's host layer's purpose. It's a shorthand, because in reality only layer groups have purpose - when a layer's purpose is queried, the extra round of getting the layer's group's purpose is automatically done. If anything matches, using the 'thus' operator the we make the result of the expression the matching _object_.
Or in plain English: "list all objects sitting in a layer group with purpose starting with ko."
When we make a copy of list A, into a new list called B (arbitrary name). Creating list A was somewhat expensive: we had to crawl all objects on the board. But the resulting list should be short, typically containing at most as many objects as many subcircuits your board has. Cloning such a list is very fast.
Now that we have our two lists A and B, we can write the actual search, using the assert instruction. The assert instruction will create a violation whenever it is evaluated to true. Since we refer to two lists, A and B, in the expression of the assert, internally it becomes two loops nested, iterating over each object in A and within that iterating over each object in B. Whenever we write A or B in our expression, that really means "the current object on list A or B", knowing the iterators are looping. Or in plain English: simply by naming A and B, we will check every A object against every B object.
First we check if A != B - that is, we do not want to check an object against itself (remember, all objects are present both on A and B)
Then we check if A's IID is less than B's IID; this dirty trick will reduce the number of redundant reports: in most cases it's either A<B or B<A, and we will deal with only one of these. (NOTE: IID is an unique integer 'instance ID' that will always differ for different objects.)
Then we check (A.layer.position == B.layer.position), so if yo ahve two objects, one in a top side layer group and the other in a bottom side layer group, we know they won't ever intersect and skip.
Then we check (A.layer.purpose == B.layer.purpose); this is the part that gets us check ko.courtyard against ko.courtyard but not ko.courtyard against ko.magnetic.
Finally the main test: overlap(A, B) - this is true if object A and object B geometrically overlap in the 2d space (not regarding their clearance setting or layer). Our final goal is to find where our courtyard objects overlap.
The \\ at the end of the line is a protected line wrap. In query this would be a single \, but we are writing this in lihata, which would interpret our \ if we didn't protect it. So \\ in lihata will emit a single \, which then will be found by the query parser.
If all the above were true, our expression is true. If we didn't do anything else here, a violation would be automatically created using the last objects evaluated, which is about right: it's an offending object from either list A or list B.
However, we want to report 2 groups of object, so both A's object and B's object is reported, as red and blue highlight. This is done using the 'thus' operator to modify what we return on true. So we use violation() to report a list of things. First our current A object in group1 and our current B object in group2. Then we also append a series of text to the description: some constant text, the layer purpose string and the drc_desc attribute of the layer (if it's missing, it'll be empty string).