Restrict

restrict can be used in coordination with require to add specific constraints on which pins are valid. We can specify all pins in a bundle to come from a specific IO bank, or that a timer has some advanced feature, or has a specific electrical property. The concept is that while supports statements are additive - the restrict statements are substractive.

The other useful feature for restrict is that it is a constraint that can be added at point of use. This allows the user to construct a general purpose component that can be used in many circuits, and then customize the pin assignment problem for a particular board design.

Signature

restrict(<REQ-PORT>, <FUNC>)
  • &lt;REQ-PORT> - Reference to a SinglePin abstract port defined by the require statement.
  • &lt;FUNC> - Reference to a function that takes a Port argument and returns a boolean (ie, True|False)
    • This function must return true if the passed Port should be an option in the solution to the pin assignment problem.
    • This function must return false if this Port should be rejected from the solution to the pin assignment problem.

Usage

When we use the restrict statement, we are typically evaluating the properties of the pins of a component or module. Here is a trivial example:

pcb-bundle gpio:
  pin single

pcb-module restrict-a-pin-set :
  port p : pin[2]
  property(p[0].active) = true
  property(p[1].active) = false

  for i in 0 to length(p) do:
    supports gpio :
      gpio.single => p[i]
  
  require g:gpio from self
  restrict(g.single,
    fn (mypin) :
      property(mypin.active))

In this example, the module has a port array p with two single pins. We then apply an active property to both of these single pins. This active property is what our restrict statement is going to check. Later the restrict statement checks whether the active property is true and only allows p[0] to be selected in the pin assignment solution.

Notice that the restrict statement is refering one of the SinglePin ports of the abstract port g of type gpio bundle. The restrict statement must refer to an abstract port (ie, a port defined by a restrict statement). If the restrict were to refer to a module or component port, this would be an error.

Here we are using an anonymous function (kind of like a lambda in python) to implement the restriction function:

fn (mypin) :
  property(mypin.active)

If you squint - this kind of looks like a function definition:

defn my-restrict-func (mypin) :
  property(mypin.active)

In fact, there is no reason you couldn't use that style of definition, you would just need to define it ahead of time:


  require g:gpio from self

  defn is-active? (mypin) :
    if has-property?(x.active):
      property(x.active)
    else:
      false

  restrict(g.single, is-active?)

NOTE - It is a good practice to make sure that all of the pins that are going to participate in this restrict functionality define the property that will be examined in the restrict function. In this example, the active property is applied to all of the pins in p - not just the pins in p that are active=true.

In the above example, the has-property? function is a useful tool to detect if/when a pin is missing a property and take a reasonable default action.

Why use a restrict?

For the most part, any restrict statement could also be implemented with a supports statement. The restrict is kind of like the DeMorgan's equivalent version.

So what is the purpose of the restrict statement if we already have supports statements?

  1. Sometimes the restrict statement version is less verbose than the equivalent supports statements.
  2. Often times the supports statement that satisfies a particular require statement are not co-located.
    1. Placing a restrict statement in the same scope as the require statement can often make the code more readable.
    2. It can be easier to document the reason for a particular restrict when it is co-located with the require
  3. The supports statements are often broad constraints that apply generally to a particular component.
    1. The restrict statement is often a refinement of those broad constraints for your particular application.
    2. This also means that we can define a general component that gets used in many circuits, and then add application specific constraints at point of use with a restrict

Examples

Gigabit Transceivers

In many FPGAs, there are often one or more classes of serializers/transceivers for high speed communications. These typically are differential pair transceivers and result in the use of a diff-pair bundle for all of them.

To differentiate between these different differential pairs, we can set a property on those differential pairs and use a restrict statement. Full listing of the example code.


defn is-gigabit-transceiver (x) -> True|False :
  if has-property?(x.gigabit-transceiver):
    property(x.gigabit-transceiver)
  else:
    false

public pcb-module gigabit-restrict-example: 

  inst fpga : Xilinx-FPGA
  inst conn : VPX-conn

  require h-dp:diff-pair from fpga
  require c-dp:diff-pair from conn

  for dp in [h-dp, c-dp] do:
    restrict(dp.P, is-gigabit-transceiver)

  ; Connect the two high-speed capable differential pairs.
  net (h-dp, c-dp)

This is the top-level of the example demonstrating the use of the restrict statement with the helper function is-gigabit-transceiver.

Notice that the restrict statement can be placed anywhere within the scope of this pcb-module. The only requirement is that it can uniquely identify which abstract port that the statement applies to.

In the restrict statement, we apply the restriction only to the dp.P, or the positive side of the differential pair. We don't have to add the dp.N restriction because it is basically implied by the supports diff-pair constraint. It wouldn't hurt anything to add it, but it would make the pin assignment problem more computationally complex.

The property gigabit-transceiver is applied in the pcb-component definition for the FPGA and the connector. Here is a slimmed down example from the FPGA:

public pcb-component Xilinx-FPGA :

  name = "Xilinx"
  description = "Excerpt of an IO Bank & High Speed Transceiver"
  manufacturer = "Xilinx"

  pin-properties :
    [pin:Ref | pads:Ref ... | side:Dir | bank:Int]
    [IO_L2N | C[3]  | Right | 0 ]
    [IO_L2P | C[2]  | Right | 0 ]
    [MGTHRXN0 | D[3] | Right | 0 ]
    [MGTHRXP0 | D[4] | Right | 0 ]

  pin-properties :
    [pin:Ref | clock-capable:(True|False) | gigabit-transceiver:(True|False)]
    [IO_L2N | false | false]
    [IO_L2P | false | false]
    [MGTHRXN0 | false | true]
    [MGTHRXP0 | false | true]

  make-box-symbol()
  assign-landpattern(FPGA-pkg)

  supports diff-pair:
    diff-pair.P => self.IO_L2P
    diff-pair.N => self.IO_L2N

  supports diff-pair:
    diff-pair.P => self.MGTHRXP0
    diff-pair.N => self.MGTHRXN0

Here we use two pin-properties tables. The first pin-properties table defines the pins, pads, and bank associations. The second pin-properties table defines other properties related to each of those pins. In particular - it defines the gigabit-transceiver property as a boolean value in the 3rd column. Notice that these properties are on a per-pin basis - not on the bundle.

At the end of the component, we define two diff-pair supports. Neither of these supports statements care about the gigabit-transceiver properties. In absence of the restrict statements, a require statement for a diff-pair bundle might select either of these two diff-pair bundles.

Example rendering in the board view:

ResultingPA

Notice that the differential pair rat's nest only connects to the MGTHRXN0/MGTHRXP0 pair on the FPGA.