The drc_query plugin is a glue layer between the query plugin and the DRC infrastructure. It allows the user (and sch import flows) to script DRC checks and use these script as part of the normal DRC workflow. This chapter describes the how to configure and use query() based DRC scripts and offers a tutorial on developing DRC scripts.
DRC rules are specified as a list of hash nodes under the plugins/drc_query/rules config path, from the config source of the user's choice. Commonly used config sources:
Example 1: a full example of an user config, saved as ~/.pcb-rnd/drc_query.conf:
li:pcb-rnd-conf-v1 { ha:overwrite { ha:plugins { ha:drc_query { li:definitions { ha:min_drill { type = coord desc = {minimum drill diameter} default = 0 } } li:rules { ha:hole_dia { type = single hole title = hole too small desc = padstack hole diameter is too small disable = 0 query = {(@.hole > 0) && (@.hole < $min_drill)} } } } } } }
There are two main subtrees below ha:drc_query: definitions and rules.
Definitions create new config nodes under design/drc. These config nodes can be used to configure parameters of the drc check. The order of definitions does not matter but the name of each definition must be unique.
A definition is a named hash subtree with the following fields:
name | mandatory | meaning |
---|---|---|
type | yes | data type of the configuration; one of: coord, boolean, integer, real, unit, color |
desc | no | human readable description |
default | no | low priority default value (format depends on type) for the case the configuration node is not set |
legacy | no | reserved for stock rules for compatibility with the old DRC, do not use in new rules |
The definition in the above example creates config node design/drc/min_drill. This new config node is accessible through the config system normally, through the conf() action or the GUI (preferences dialog). From query(), it can be referenced using the shorthand $min_drill form.
Text fields type, title and desc are optional user readable strings that will be presented in the DRC report. The type::title pair serves as an unique identifier for the rule. If optional disable is 'yes' or 'true' or a non-zero integer value, the rule is temporarily disabled.
Note: since drc errors are presented on a per type basis, and drc rules are executed each in its own, independent context, the order of the hash nodes within the rules list (merged from different config sources) determines the order in which tests are performed, but otherwise does not matter (as tests are independent).
Rules optionally can have a source field, which is a one word free form text, indicating where the rule is coming from. User specified/configured rules should leave this field empty. Rules imported from the netlist (or schematics) should set this field to netlist. When present, the GUI will display rules from the same source grouped. But the real purpose of the source field is to make re-import of rules possible: when a new import from the same source is performed, the import code should remove all rules coming from the source and create new rules with the same source.
Query text may consists of multiple lines. The format is as described at the query plugin. There are two alternate forms:
Example 2: a rule based query script example is finding overlapping drilled holes:
query = { rule overlap let A @.type==PSTK let B A assert (A.ID > B.ID) && (distance(A.x, A.y, B.x, B.y) < (A.hole + B.hole)/2) thus violation(DRCGRP1, A, DRCGRP2, B) }
Note: since drc errors are presented on a per type basis, and drc rules are executed each in its own, independent context, the order of rules does not matter.
The default drc_query config shipped with pcb-rnd contains a few stock DRC rules. These are the most essential rules that most boards will need. (Users are encouraged to download further scripts to extend the DRC system.) This chapter documents the stock DRC scripts and the checks they perform.
Lists holes with diameter smaller than the board-global minimum (set in $min_drill). Note: only round holes (hole diameter) are checked, which are always in padstacks. Slots (in padstacks) or mech/boundary layer explicit object are not checked.
Checks pairs of holes (as defined for hole_dia above) and list pairs that have any overlap between their drilled hole. (Some fabs may refuse to drill such holes because of increased risk of breaking drill bits.)
Check every overlapping pair of copper objects within known nets. Throw a DRC violation if the overlap offset is smaller than $min_copper_overlap. (Although not very likely with modern CAM and fab processes, but insufficient overlap may cause glitches or even broken net.)
Check every pair of copper objects within known nets. Throw a DRC violation if two objects of different nets have non-copper gap less than $min_copper_clearance.
Check each copper object with thickness (arcs and lines) and throw a violation for those that are thicker than 0 but thinner than $min_copper_thickness. (The exception for 0 is provided for compatibility. Please use the noexport mechanism instead.)
Same as min_copper_thickness but runs on silk objects and uses $min_silk_thickness.
List any object whose bounding box is fully beyond the drawing area in any direction. This is useful for detecting copy&paste or object move leftovers, especially if the leftover is a small object.
For each padstack calculate the neck size, which is the shortest straight line that can be drawn from the inner (hole/slot) contour and the outer (copper) contour of the padstack. If the neck size is below $min_ring, throw a violation.
Lists any polygon that has the FULLPOLY flag and indeed has multiple islands. The fullpoly feature is provided only for compatibility and should not be used: it may break the DRC and connection finding in some common cases.
Courtyard/keepout: warn for pairs of objects that overlap on layer groups with purpose string starting with "ko.". The most common use is part mechanical courtyard. Please refer to the ko_id pool node for detailed documentation.
The polygon lib of pcb-rnd requires that polygon contours to be not self intersecting. When pcb-rnd is configured with --debug, a lot of checks are ran runtime and the code asserts/aborts as early as possible when detecting invalid polygon contour. However, in production compilation these checks are disabled. The only way an user of a production pcb-rnd can find all invalid polygons on a board is running this test. Note: invalid polygons will eventually break something on the board: the on-screen render, an export render or polygon clipping.
In Example 1, a single query() expression is used: it iterates through all objects available (@) and evaluates whether the object's hole property is smaller than a predefined value. This expression can result in three different outcome for any object:
Such simple, single-expression DRC rules are common for checks that need to decide whether every single object matching some criteria passes a test on its properties. It works for Example 1 because each padstack object can be checked separately from any other object and the outcome of a check depends only on properties of the single padstack object and constants. There is only one iterator used, @, which iterates on all objects of the board.
Note: the left-side check of && for hole diameter greater than zero is needed because of the pcb-rnd data model: hole diameter zero means no hole (e.g. smd pads) - no warning should be issued for that.
However, in some situations a DRC violation depends on two or more objects, thus iterations need to be done on pairs (or tuples) of objects. It is done by:
Example 2 demonstrates a simple case of pair-wise iteration: first a list called A is set up; it will contain all objects of the board whose type is PSTK. Then list A is copied to B in the second let.
Note: the second argument of a let is always an expression. In the first case it is more trivial: (@.type==PSTK). When this expression is true, the last evaluated object (the current iteration with @) is appended to the destination list (specified in the first argument). The second let "copies" the list by expression (A). It really means: having the iterator run on each item of A, evaluating each; they will all be true (since they are existing objects); once an item is true, the last evaluated object (the same that caused the expression to be true) is appended to the left side-list B. The part that may be confusing is this: on the left side, there's a list name, on the right side there is an expression. Referencing a list within an expression will always result in iteration and is always substituted by a single item of the list.
Once the lists are set up, an assert is specified with an expression that uses both A and B. This makes the query engine iterate in nested loops for items of A and B, which means the expression is evaluated to every possible pair of items in list A and list B.
Or in other words, any possible pair of padstacks on the board - including the case of a virtual pair of the same padstack twice (since every padstack is present on both lists). For example if the board has three padstacks p1, p2 and p3, then A is [p1, p2, p3] and B is [p1, p2, p3] too. The first iteration will take the first item of A and the first item of B, so the pairing is p1 vs. p1. Avoiding such false checks is easy by starting the assert expression by (A != B), which will make the whole expression false for the virtual pair case.
Note: query has lazy evaluation so when the left side of an && if false, the right side is not evaluated. This it makes execution more efficient to add such simple checks that limit the number of objects or combinations to deal with as early as possible in the expression.
With that, we would be checking the same pairs twice still: p1 vs. p2 and then p2 vs. p1. The easiest way to avoid that is to consider that each object has an unique integer ID that can be compared, which allows us to write (A.ID > B.ID) instead of (A != B). This ensures only 'ordered pairs' are considered, at about the same low cost of check.
The left side of the && filters duplicates and self-overlaps. The right side is the actual check: it calculates the distance of the hole centers using the distance() builtin function and compares the result to the expected minimum distance which is the sum of the radii of the two holes. Since a hole is always at the origin of the padstack, the hole's center coordinate matches the padstack's coordinate. When A.x is written in the expression, that really means: "take the object from list A for the current iteration and extract its x property", which for a padstack means the x coordinate of the padstack placement.
When both the left side and the right side of the && evaluates to true, that means the pair is valid and unique and the holes overlap. At this point drc_query could already indicate the DRC violation, but it would use the last evaluated object for indicating the location. Which means the indication would mark only one of the two padstacks participating in the overlap.
The thus keyword changes this: when the left side of thus evaluates to true, instead of returning true, the right side is evaluated and returned. The right side is a function call to the builtin function violation, which will create a new list of instruction-objects pairs, inserting the current iteration object from list A as group-1 object and list B as group-2 object. In other words, it's a list of the two padstack objects participating in the overlap, sorted into two groups (red and blue on screen).
Call arguments for violation() are always pairs of a DRC* constant and an object or numeric value. There can be 1 or more pairs. The following DRC* constants can be used:
name | meaning |
---|---|
DRCGRP1 | following object is in group-1 of offending objects |
DRCGRP2 | following object is in group-2 of offending objects |
DRCEXPECT | following numeric constant is DRC-expected value (e.g. minimum or maximum) |
DRCMEASURE | following numeric constant is the measured value (that contradicts the expected value) |
DRCTEXT | following string or value is appended to the violation description as plain text |
Using any of the above instructions is optional, with the following considerations: