The eval-when Statement

When constructing a component or module, there are often times checks we want to write that are dependent on the application. We need to know more about the other circuits we are connected to before we can write the check. To delay the evaluation of these checks until we have the information we need - we use the eval-when statement.

The eval-when statement is valid in the following contexts:

  1. pcb-component
  2. pcb-module

The eval-when statements work in tandem with the run-evals command. The run-evals function traverses the passed module and orchestrates the running of various eval-when blocks. This means for eval-when statements in your modules/components to run - you must call run-evals at some point after you have completed your design declarations.


eval-when <CONDITION> :

  • The &lt;CONDITION> is a predicate (ie, something that evaluates to True|False). This condition indicates what data needs to be present in order for this eval-when to run.
  • The &lt;STATEMENT-1> is a list of statements that are valid for the current context. This list of statements is typically called the body of the eval-when statement. This can be general stanza code or any valid pcb-component or pcb-module statements, depending on their respective contexts.


The eval-when statement is a powerful method of customizing modules for the context in which those modules are instantiated. This method allows us to keep the logic that applies to a particular module or component co-located with the definition of that module or component.

pcb-component LDO :

    [pin:Ref | pads:Int ... ]
    [VIN     | 1            ]
    [GND     | 2            ]
    [VOUT    | 3            ]


  property(self.VOUT.voltage) = 3.3

  property(self.MAX_VIN) = 12.0
  property(self.MAX_DROP_OUT) = 0.2

  eval-when has-property?(self.VIN.voltage) :
    val max-rating = property(self.MAX_VIN)
    if self.VIN.voltage > max-rating:
        "Invalid Input Voltage - %_ exceeds max rating of %_" %
        [self.VIN.voltage, max-rating]

    val min-rating = property(self.VOUT.voltage) + property(self.MAX_DROP_OUT)
    if self.VIN.voltage < min-rating : 
        "Invalid Input Voltage - %_ is less than min rating of %_" %
        [self.VIN.voltage, min-rating]

In this case, we're defining a simple, fixed-voltage LDO regulator. We want to check that the voltage applied to the VIN port is within the expected and allowed range for this component.

For these checks to work - we need to know what voltage is being applied to the VIN port and we don't necessarily know that when the component is instantiated. The eval-when is waiting for the application of a voltage property on the VIN port. Some other entity is going to apply this at the application layer.

Once the voltage property exists on VIN, the statements in the body will execute. In this case we do some minimal checks on the accepted voltage range of VIN.

With Great Power - Comes Great Responsibility

You might notice that this adds some conditional logic to what would otherwise be a purely declarative component or module. This is a concept that doesn't really exist in other legacy CAD tools - ie, you don't typically have to worry about the components mutating.

The safest operations to use inside an eval-when clause are operations that don't modify the physical nature of the PCB:

  1. Checks - These are typically non-mutating and only read properties or structures.
  2. BOM Variations - Changing the BOM or any variants is usually OK.
  3. Adding no-connect() statements
  4. Adding property() statements

Operations that are more difficult to use consistently in an eval-when body include:

  1. Adding inst and net statements
  2. Adding concrete ports with the port statement.
  3. Adding new abstract ports with the require statement.

There are certainly cases where you might want to use these statements in an eval-when body. These statements are supported and will execute as part of the design run. We suggest proceeding with care and purpose when using these statements.