A DRC program is an unordered list of rules. Rules are evaluated and violations reported. The advantage of a declarative language is that intermediate results can be cached and reused.
The language is intended to be human readable and human writable, but the main goal is to let programs and scripts (e.g. netlisters) to generate it.
A rule consists of three parts:
A simpler variant of a rule is a single expression, without any rule or let or assert keywords in it.
Various parts of pcb-rnd, including the advanced search feature and the query() action, runs a DRC rule (typically the simplified, single-expression variant) to find/select/list drawing objects.
(Note: the language does not support comments, but the files scripts are loaded from usually do. Please refer to the file format specification of those files, typically lihata or tEDAx. This also means such comments are not loaded into memory and can not be seen or edited on the GUI.)
Variables are named by the user and are local to the rule. A variable always holds a list of objects. Lists are ordered. A list is consists of zero or more objects. An object is on of the following:
Objects have named properties (or fields):
Note: the language is case sensitive with keywords and builtins using lowercase only. For better readability, in syntax description in this document uppercase words are user chosen identifiers or fields. (This does not mean identifiers have to be uppercase in a program.)
Whitespace character sequences are usually treated as a single whitespace.
The syntax of a search statement that stores a result in a list is:
let LISTNAME EXPR
It creates a list called LISTNAME and evaluates expression EXPR to all available objects and adds the objects that match EXPR to the list. Each matching object is added only once.
The particular order of objects on the list depends on how iterations are taken in EXPR. For example if EXPR refers to only one list (e.g. @), the ordering of that list is preserved in the result as well.
Object "matches EXPR" when the EXPR evaluated on the object yields true.
A special list that always exist is called @, and contains all subcircuits and drawing objects present on the board (including those that are part of subcircuits), in random order.
An expression is evaluated to a value. A value can be:
A value is considered true if:
An expression is one of:
syntax | meaning |
---|---|
(EXPR) | change precedence |
EXPR || EXPR | logical OR (result: number) |
EXPR && EXPR | logical AND (result: number) |
EXPR1 thus EXPR2 | evaluate to EXPR2 if EXPR1 is true, else to void |
EXPR + EXPR | add (numbers only) |
EXPR - EXPR | subtract (numbers only) |
EXPR * EXPR | multiply (numbers only) |
EXPR / EXPR | divide (numbers only) |
EXPR == EXPR | the two values are equal (result: number) |
EXPR != EXPR | the two values are not equal (result: number) |
EXPR ~ string | extended regex match left EXPR using pattern right string (result: number) |
EXPR > EXPR | left EXPR is greater than right EXPR (number only) |
EXPR >= EXPR | left EXPR is greater than or equal to right EXPR (number only) |
EXPR < EXPR | left EXPR is less than right EXPR (number only) |
EXPR <= EXPR | left EXPR is less than or equal to right EXPR (number only) |
!EXPR | logical NOT (result: number, 0 or 1) |
FUNC(EXPR, EXPR, ...) | call a function with 0 or more arguments |
EXPR.field | evaluated to the value of an object field (see P45, P46) |
The syntax of an assertion is:
assert EXPR
When running the DRC, if the EXPR in an assert evaluates to false, a DRC violation is generated. The return value of such an expression should normally be a list generated using the violation() function, so that it can pass on all relevant details (such as expected value, affected objects) to the DRC display.
When running a search, normally the result of an assert should be an object.
If an EXPR references a variable (list), it is evaluated for all valid members of the list, in order of the list. For example if there is a variable called FOO, which is a list of objects (built using a search statement), expression
FOO.p.thickness
is evaluated as many times as many objects are on the list. If there is another similar list called BAR, an expression:
(FOO.p.thickness < BAR.p.thickness)
will compare each possible pair of FOO and BAR objects. That is, if FOO has 4 objects and BAR has 15 objects, that is 4*15 = 60 comparisons.
However, each list is iterated only once, even if it is referenced multiple times in the same expression. For example, with the above lists:
(FOO.p.clearance > 10 mil) && (FOO.p.thickness < BAR.p.thickness)the potential number of iterations is still 4*15, and not 4*4*15 (FOO is not iterated twice). In practice the engine leverages lazy evaluation so if FOO.p.clearance is smaller than 10 mil, the right side is not evaluated, which saves a few comparisons. See also: fields.
If a function needs to be called with a whole list passed instead of calling to function for each element of the list, the special list() built-in function should be used. For example assume FOO is a list of 5 objects:
llen(list(FOO))
will pass FOO as a list to function llen(), which is called only once. Without the list() wrapping, llen() would be called five times, once for each item in the list.
A field reference is valid if the field exists. For example a line object has a thickness attribute, thus the .p.thickness is valid, but a polygon object does not have a thickness and .p.thickness on a polygon is invalid. An user attribute reference (e.g. field .a.baz) is valid if the attribute key exists in the given object.
Invalid fields evaluate to value void (and is not considered an error). Thus if variable BLOBB is a list that consists of 3 line, 2 arc and a layer objects, the following expression will be void (false) for at least the 3 line objects and can be true for at most 2 cases (the arc objects):
(BLOBB.p.width >= 10 mm)(because only arc objects have valid .p.width field, which is the horizontal radius of an arc).
A void value is never equal to anything. A void value is not equal even to another void value.
User functions are defined with the function keyword, followed by the function name and a comma separated list of argument names in parenthesis. The argument list may be empty. Each argument passed must be an object or a list (i.e. numbers can not be passed).
The body of the function may contain the same constructs as the body of a rule.
A function definition ends with a return keyword followed by an expression. The experession is evaluated and the resulting value is returned. If the expression iterates, the last evaluated value is returned.
Example (counts the number of non-terminal, holed padstacks on a net object called LNET):
function count_vias(LNET) let OBJS netobjs(LNET) let VIAS (OBJS.type == PSTK) && (OBJS.hole > 0) && (OBJS.a."term" == "") return llen(list(VIAS))
On the calling side user functions are the same as built-in functions, e.g. the above functions can be called as:
let ALLNETS netlist() assert (count_vias(ALLNETS) != 4)
Comments are lines starting with #