drc_query: query() based, scriptable Design Rule Checker

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.

Configuration

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

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_dirll form.

Rules

Text fields type, tilte 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 groupped. 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.

Tutorial

Single expression scripts

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.

Rule scripts

However, in some situations a DRC violation depends on two or more objects, thus iterations need to be done on pairs (or tuplets) 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 expession 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 od (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 evaulated 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: