Four Problems with Policy-Based Constraints and How to Fix Them

Originally presented at DVCon US 2024

Table of contents

Constraints and Policy Class Review

Random objects and constraints are the foundational building blocks of constrained random verification in SystemVerilog. The simplest implementations embed fixed constraints within a class definition. Embedded constraints lack flexibility; all randomized object instances must meet these requirements exactly as they are written.

In-line constraints using the with construct offer marginally better flexibility. Although these external constraints allow greater variability of random objects, their definitions are still fixed within the calling context. Furthermore, all in-line constraints must be specified within a single call to randomize().

Policy classes are a technique for applying SystemVerilog constraints in a portable, reusable, and incremental manner, originally described by John Dickol [1][2]. The operating mechanism leverages an aspect of "global constraints," the simultaneous solving of constraints across a set of random objects. Randomizing an object that contains policies also randomizes the policies. Meanwhile, the policies contain a reference back to the container. Consequently, the policy container is constrained by the policies it contains. Dickol's approach is illustrated by the following code.

class policy_base#(type ITEM=uvm_object);
    ITEM item;

    virtual function void set_item(ITEM item);
        this.item = item;
    endfunction
endclass

class policy_list#(type ITEM=uvm_object) extends policy_base#(ITEM);
    rand policy_base#(ITEM) policy[$];

    function void add(policy_base#(ITEM) pcy);
        policy.push_back(pcy);
    endfunction

    function void set_item(ITEM item);
        foreach(policy[i]) policy[i].set_item(item);
    endfunction
endclass

Figure 1. The policy_base and policy_list classes

These two base classes provide the core definitions for policies: policy_base implements the hook back to the policy container, and policy_list enables related policies to be organized into groups. Both classes are parameterized by a container object type, so a unique specialization will be required for each policy-enabled container. Policy containers like transactions, sequences, and configuration objects implement these classes to support flexible random steering. Below are examples of a generic transaction, addr_txn , with a random address and size and some policy classes to constrain those attributes.

class addr_txn;
    rand addr_t addr;
    rand int    size;
    rand policy_base#(addr_txn) policy[$];

    constraint c_size {size inside {1, 2, 4};}

    function void pre_randomize;
        foreach(policy[i]) policy[i].set_item(this);
    endfunction
endclass

class addr_policy_base extends policy_base#(addr_txn);
    addr_range ranges[$];

    function void add(addr_t min, addr_t max);
        addr_range rng = new(min, max);
        ranges.push_back(rng);
    endfunction
endclass

class addr_permit_policy extends addr_policy_base;
    rand int selection;

    constraint c_addr_permit {
        selection inside {[0:ranges.size()-1]};

        foreach(ranges[i]) {
            if(selection == i) {
                item.addr inside {[ranges[i].min:ranges[i].max - item.size]};
            }
        }
    }
endclass

class addr_prohibit_policy extends addr_policy_base;
    constraint c_addr_prohibit {
        foreach(ranges[i]) {
            !(item.addr inside {[ranges[i].min:ranges[i].max - item.size + 1]});
        }
    }
endclass

Figure 2. The addr_txn class and policies for constraining it

The addr_permit_policy and addr_prohibit_policy classes implement some policies for constraining addr_txn addresses. Address ranges can be stored in the ranges array. The addr_permit_policy will choose one of the ranges at random and constrain the address to be within the range, while the addr_prohibit_policy will exclude addresses that fall within any of the ranges in its list.

The final class shows how policies might be used. The addr_constrained_txn class extends addr_txn and defines two policies, one that permits the address to be within one of two ranges, and one that prohibits the address from being within a third range. The addr_constrained_txn class then passes the local pcy list to the parent policy queue.

class addr_constrained_txn extends addr_txn;
    function new;
        addr_permit_policy     permit = new;
        addr_prohibit_policy   prohibit = new;
        policy_list#(addr_txn) pcy = new;

        permit.add('h00000000, 'h0000FFFF);
        permit.add('h10000000, 'h1FFFFFFF);
        pcy.add(permit);

        prohibit.add('h13000000, 'h130FFFFF);
        pcy.add(prohibit);

        this.policy = {pcy};
    endfunction
endclass

Figure 3. The addr_constrained_txn subclass

At this point, an instance of addr_constrained_txn can be created and randomized like normal, and the address will be constrained based on the embedded policies.

addr_constrained_txn txn = new(); 
txn.randomize();

Figure 4. Randomizing an instance ofaddr_constrained_txn

Further work on policy-based constraints has been presented since the original DVCon presentation in 2015. Kevin Vasconcellos and Jeff McNeal applied the concept to test configuration and added many nice utilities to the base policy class [3]. Chuck McClish extended the concept to manage real number values for User Defined Nettypes (UDN) and Unified Power Format (UPF) pins in an analog model [4]. Additionally, McClish defined a policy builder class that was used to generically build multiple types of policies while reducing repeated code that was shared between each policy class in the original implementation.

Although there has been extensive research on policies, this paper aims to address and provide solutions for three issues that have not been adequately resolved in previous implementations. Furthermore, a fourth problem that arose during testing of the upgraded policy package implementation will also be discussed and resolved.

Problem #1: Parameterized Policies

The first problem with the above policy implementation is that because policy_base is parameterized to the class it constrains, different specializations cannot be grouped and indexed. The awkward consequences of this limitation become apparent when using policies with a class hierarchy. If you extend a class and add a new random field then you need a new policy type to constrain that field. The new policy type requires its own policy list, and the new list must be traversed and mapped back to the container during pre_randomize().

Imagine a complex class hierarchy with several layers of inheritance and extension, and constrainable attributes on each layer (one common example is a multi-layered sequence API library, such as the example presented by Jeff Vance [5]). Using policies becomes cumbersome in this case because each class layer requires a unique family of constraints organized into a distinct list. This stratification of polices places a burden on users to know which layer of the class hierarchy defines each attribute they want to constrain, and the name of the associated policy list for the matching policy type. For example, extending the addr_txn class to create a version with parity checking results in a class hierarchy that looks like this:

class addr_txn;
  // ... unchanged from previous example
endclass

class addr_p_txn extends addr_txn;
  rand bit parity;
  rand bit parity_err;
  rand policy_base#(addr_p_txn) addr_p_policy[$];

  constraint c_parity_err {
    soft (parity_err == 0);
    (parity_err) ^ ($countones({addr, parity}) == 1);
  }

  function void pre_randomize;
    super.pre_randomize();
    foreach(addr_p_policy[i]) addr_p_policy[i].set_item(this);
  endfunction
endclass

class addr_constrained_txn extends addr_p_txn;
  function new;
    addr_permit_policy        permit_p = new;
    addr_prohibit_policy      prohibit_p = new;
    
    // definition to follow in a later example - constrains parity_err bit
    addr_parity_err_policy    parity_err_p = new;

    policy_list#(addr_txn)    addr_pcy_lst = new;
    policy_list#(addr_p_txn)  addr_p_pcy_lst = new;

    permit_p.add('h00000000, 'h0000FFFF);
    permit_p.add('h10000000, 'h1FFFFFFF);
    addr_pcy_lst.add(permit_p);

    prohibit_p.add('h13000000, 'h130FFFFF);
    addr_pcy_lst.add(prohibit_p);
    
    parity_err_p.set(1'b1);
    addr_p_pcy_lst.add(parity_err_p);

    this.addr_policy   = {addr_pcy_lst};
    this.addr_p_policy = {addr_p_pcy_lst};
  endfunction
endclass

Figure 5. Modified address transaction classes with a parity checking subclass and policies included

Scaling this implementation results in a lot of repeated boilerplate code and is not very intuitive to use. Each additional subclass in a hierarchy only increases the chaos and complexity of implementing and using policies effectively.

The solution to this problem is to replace the parameterized policy base with a non-parameterized base and a parameterized extension. We chose an interface class as our non-parameterized base for the flexibility it offers over a virtual base class---specifically, our policies are bound only to implement the interface functions and not to extend a specific class implementation.

interface class policy;
    pure virtual function void set_item(uvm_object item);
endclass

virtual class policy_imp#(type ITEM=uvm_object) implements policy;
    protected rand ITEM m_item;

    virtual function void set_item(uvm_object item);
        if (!$cast(m_item, item)) begin
            `uvm_warning("policy::set_item()", "Item/policy type mismatch")
            this.m_item = null;
            this.m_item.rand_mode(0);
        end
    endfunction: set_item
endclass: policy_imp

typedef policy policy_queue[$];

Figure 6. The policy interface class and policy_imp class

A non-parameterized base enables all policies targeting a particular class hierarchy to be stored within a single common policy_queue. A parameterized template, policy_imp , implements the base interface and core functionality required by all policies.

One consequence of eliminating the parameter from our base type is that the policy-enabled container object, item , and its assignment function, set_item() , are no longer strongly typed. Here we make a small concession, using uvm_object as our default policy-enabled type. This means that all classes that implement our policies must derive from uvm_object , and we need to use dynamic casting to ensure that policies and their containers are type-compatible. In the example above, item is set to null and randomization is disabled when the cast fails, preventing runtime problems in the event that incompatible policies are applied.

Not much changes when it comes to defining policies; the address policies now extend policy_imp instead of policy_base , and the underlying constraints are written as implications so that they will not apply when item is missing.

class addr_policy extends policy_imp#(addr_txn);
   // ... unchanged from previous example, with updated class extension
endclass

class addr_parity_err_policy extends policy_imp#(addr_p_txn);
   protected bit parity_err;

   constraint c_fixed_value {m_item != null -> m_item.parity_err == parity_err;}

   function new(bit parity_err);
      this.parity_err = parity_err;
   endfunction
endclass

class addr_permit_policy extends addr_policy;
   // same as before
endclass

class addr_prohibit_policy extends addr_policy;
   // same as before
endclass

Figure 7. Address policies updated to use the new policy implementation

However, the address transaction classes are simplified considerably. Only a single policy queue is required in the class hierarchy, and the vast majority of the boilerplate code has been eliminated, including all of the specialized lists. The addr_txn class now extends uvm_object to provide compatibility with the policy interface.

class addr_txn extends uvm_object;
    rand policy_queue policies;
    // policy is replaced with the above. All other members, constraints, 
    // and pre_randomize are unchanged from the previous example
endclass

class addr_p_txn extends addr_txn;
    rand bit parity;
    rand bit parity_err;
    constraint c_parity_err {/*...*/}
    // The local addr_p_policy and pre_randomize are removed. Everything
    // else is unchanged from the previous example
endclass

class addr_constrained_txn extends addr_p_txn;
    function new;
        addr_permit_policy     permit_p   = new();
        addr_prohibit_policy   prohibit_p = new();
        addr_parity_err_policy parity_err_p;

        // only a single policy queue is necessary now
        permit_p.add('h00000000, 'h0000FFFF);
        permit_p.add('h10000000, 'h1FFFFFFF);
        this.policies.push_back(permit_p);

        prohibit_p.add('h13000000, 'h130FFFFF);
        this.policies.push_back(prohibit_p);
        
        parity_err_p = new(1'b1);
        this.policies.push_back(parity_err_p);
    endfunction
endclass

Figure 8. Address transaction classes updated to use the new policy implementation

Problem #2: Definition Location

The second problem with policies is "where do I define my policy classes?" This is not a complicated problem to solve; most users will likely wish to place their policy classes in a file or files close to the class they are constraining. However, directly embedding policy definitions within the class they constrain offers a myriad of benefits. Not only does this convention eliminate all guesswork about where to define and discover policies, but embedded policies also gain access to all members of their container class, including protected properties and methods! This privileged access enables policies to constrain attributes of a class that are not otherwise exposed, improving encapsulation.

To further optimize the organization of potentially large families of policies, we establish a convention of defining all policy classes within an embedded wrapper class called POLICIES. Each layer of a class hierarchy that implements policies will have its own embedded POLICIES wrapper, and individual POLICIES wrappers extend other wrappers in a manner parallel to their container classes. This parallel inheritance pattern is shown below, with addr_p_txn::POLICIES extending addr_txn::POLICIES.

class addr_txn extends uvm_object;
    // class members, constraints, and pre_randomize unchanged from previous

    class POLICIES;
        class addr_policy extends policy_imp#(addr_txn);
            // ... unchanged from previous standalone class example
        endclass

        class addr_permit_policy extends addr_policy;
            // ... unchanged from previous standalone class example
        endclass

        class addr_prohibit_p_policy extends addr_policy;
            // ... unchanged from previous standalone class example
        endclass
    endclass: POLICIES
endclass

class addr_p_txn extends addr_txn;
    protected rand bit parity_err;
    // other class members and constraints unchanged from previous example

    class POLICIES extends addr_txn::POLICIES;
        class addr_parity_err_policy extends policy_imp#(addr_p_txn);
            // ... unchanged from previous example
        endclass

        static function addr_parity_err_policy PARITY_ERR(bit value);
            PARITY_ERR = new(value);
        endfunction 
    endclass: POLICIES
endclass

class addr_constrained_txn extends addr_p_txn;
    function new;
        addr_constrained_txn::POLICIES::addr_permit_policy   permit_p   = new();
        addr_constrained_txn::POLICIES::addr_prohibit_policy prohibit_p = new();

        // policy constraint value setup unchanged from previous example
        
        this.policies.push_back(
            addr_constrained_txn::POLICIES::PARITY_ERR(1'b1)
        );
    endfunction
endclass

Figure 9. Address transaction classes with embedded policies

This example also shows how we can define static constructor functions within POLICIES wrappers. This practice further reduces the cost of using policies since we can instantiate and initialize them with a single call, as demonstrated with the call to addr_constrained_txn::POLICIES::PARITY_ERR(). Note that although the PARITY_ERR constructor is defined in addr_p_txn::POLICIES , it is accessible through addr_constrained_txn::POLICIES because of the wrapper class inheritance. The POLICIES:: scoping layer even helps to make code more readable and easy to understand.

What's more, the parity_err property has now been defined as protected , preventing anything but our PARITY_ERR policy from manipulating that "knob." In fact, a more advanced use of policies might define all members of a target class as protected, restricting the setting of fields exclusively through policies and reading through accessor functions, thus encouraging maximum encapsulation/loose coupling, which reduces the cost of maintaining and enhancing code and prevents bugs from cascading into classes that use policy-enabled classes.

Problem #3: Boilerplate Overload

The third problem with using policies is that policies are relatively expensive to define since you need at a minimum: a class definition, a constructor, and a constraint. This will be relatively unavoidable for complex policies, such as those defining a relationship between multiple specific class attributes. For generic policies, such as equality constraints (property equals X), range constraints (property between Y and Z), or set membership (keyword inside ) constraints, macros can be used to drastically reduces the expense and risk of defining common policies. The macros are responsible for creating the specialized policy class for the required constraint within the target class, as well as a static constructor function that is used to create new policy instances of the class. Additional macros are utilized for setting up the embedded POLICIES class within the target class. These macros can be used hand in hand with the non-macro policy classes needed for complex constraints, if necessary.

// Fixed-value policy class and constructor macro
`define fixed_policy(POLICY, FIELD, TYPE)                    \
`m_fixed_policy_class(POLICY, FIELD, TYPE)                   \
`m_fixed_policy_constructor(POLICY, TYPE)

`define m_fixed_policy_class(POLICY, FIELD, TYPE)            \
    class POLICY``_policy extends base_policy;               \
        constraint c_fixed_value {                           \
            (m_item != null) -> (m_item.FIELD == l_val);     \
        }                                                    \
                                                             \
        function new(TYPE value);                            \
            this.l_val = value;                              \
        endfunction                                          \
    endclass: POLICY``_policy

`define m_fixed_policy_constructor(POLICY, TYPE)             \
    static function POLICY``_policy POLICY(TYPE value);      \
        POLICY = new(value);                                 \
    endfunction: POLICY

Figure 10. Macros for setting up the embedded POLICIES class and a fixed value policy

This example includes a `fixed_policy macro, which wraps two additional macros responsible for creating a policy class and a static constructor for the class. This `fixed_policy example policy class lets you constrain a property to a fixed value. A more complete macro definition can be found in the appendix. The appendix includes `start_policies , `start_extended_policies , and `end_policies macros that are used to create the embedded POLICIES class within the constrained class instead of using hard-coded classand endclass statements. They set up class inheritance as needed and create a local typedef for the policy_imp parameterized type.

class addr_txn extends uvm_object;
    // class members, constraints, and pre_randomize unchanged from previous example

    `start_policies(addr_txn)
        `include "addr_policies.svh"
    `end_policies
endclass

class addr_p_txn extends addr_txn;
    // class members and constraints unchanged from previous example

    `start_extended_policies(addr_p_txn, addr_txn)
        `fixed_policy(PARITY_ERR, parity_err, bit)
    `end_policies
endclass

class addr_constrained_txn extends addr_p_txn;
    // ... unchanged from previous example
endclass

Figure 11. Simplified address transaction classes using the policy macros

The base addr_txn class has complex policies with a relationship between the addr and size fields, so rather than creating a policy macro that will only be used once, they can either be left as-is within the embedded policies class, or moved to a separate file and included with `include as was done here to keep the transaction class simple. The child parity transaction class is able to use the `fixed_policy macro to constrain the parity_err field. The constraint block remains the same as the previous example.

Problem #4: Unexpected Policy Reuse Behavior and Optimizing for Lightweight Policies

A fourth problem occurred during our initial deployment of policies. We observed occasional unexpected behavior when attempting to re-randomize objects with policies. We didn't thoroughly characterize the behavior, but sometimes policies seemed to "remember" previous randomizations and wouldn't reapply their constraints during subsequent randomize calls. Results would clearly violate even simple policies.

Our policy architecture prioritizes scalability; policy classes are lightweight with a minimal footprint. Rather than investing effort to diagnose and work around the problem with reusing policies, we adopted a "use once and discard" approach, leaning into their disposable nature. It costs little to apply fresh policy instances before re-randomizing a target object. Following this strategy completely eliminated policy misbehavior.

To facilitate a safer form of policy reuse we introduced a copy method that returns a fresh policy instance initialized to the same state as the policy that implements it. We also doubled down on our use of static constructors to generate initialized policies.

static function addr_parity_err_policy PARITY_ERR(bit value); 
    PARITY_ERR = new(value); 
endfunction 
... 
this.policies = 'addr_constrained_txn::POLICIES::PARITY_ERR(1'b1);

Figure 12. Example static constructor function from the PARITY_ERR policy class

Passing array literals populated by policy instances from static constructors proved to be an excellent way to pack a lot of intent into little code. It also neatly worked around the reliability issues of reused policies.

More Improvements to the Policy Package

The examples presented so far are functional, but are lacking many features that would be useful in a real-world implementation. The following examples will present additional improvements to the policy package that will make it more practical and efficient to use.

Expanding the policy interface class

The following policy interface class adds additional methods for managing a policy.

interface class policy;

    pure virtual function string name();
    pure virtual function string type_name();
    pure virtual function string description();
    pure virtual function bit item_is_compatible(uvm_object item);
    pure virtual function void set_item(uvm_object item);
    pure virtual function policy copy();

endclass: policy

Figure 13. Expanded policy interface class

The name , description , and copy methods are implemented by the policy (or policy macro) directly and provide reporting information useful when printing messages about the policy to the log for the former two, or specific behavior for making a copy for the latter. The remaining three methods are implemented by policy_imp and are shared by all policies.

Better type safety checking and reporting in policy_imp methods

Some of the benefits of above methods can be seen by examining the new set_item method used by policy_imp.

virtual function void set_item(uvm_object item);
    if (item == null) begin
        `uvm_error("policy::set_item()", "NULL item passed")

    end else if ((this.item_is_compatible(item)) && $cast(this.m_item, item)) begin
        `uvm_info(
            "policy::set_item()",
            $sformatf(
                "policy <%s> applied to item <%s>: %s",
                this.name(), item.get_name(), this.description()
            ),
            UVM_FULL
        )
        this.m_item.rand_mode( 1 );

    end else begin
        `uvm_warning(
            "policy::set_item()",
            $sformatf(
                "Cannot apply policy '%0s' of type '%0s' to target object '%0s' of incompatible type '%0s'",
                this.name(), ITEM::type_name(), item.get_name(), item.get_type_name()
            )
        )
        this.m_item = null;
        this.m_item.rand_mode( 0 );
    end
endfunction: set_item

Figure 14. The set_item method from policy_imp

The set_item method makes use of all the reporting methods to provide detailed log messages when set_item succeeds or fails. Additionally, the item_is_compatible is used before the \$cast method is called and the rand_mode state is kept consistent with the result of the cast.

Replacing policy_list with policy_queue

Eagle-eyed readers might have noticed the lack of presence of a policy_list class in any of the examples above after migrating to the improved policy interface. Rather, a single typedef is all that is necessary to manage policies in a class.

typedef policy policy_queue[$];

Figure 15. The policy_queue typedef

The policy_queue type is capable of storing any policy that implements the policy interface. The default queue methods are sufficient for aggregating policies, and in practice we found that using policy queues as containers was more efficient than policy_list instances. For example, for functions expecting a policy_queue argument we can directly pass in array literals populated by calls to static constructor functions, allowing us to define, initialize, aggregate, and pass policies all in a single line of code!

Standardize policy implementations with the policy_container interface and policy_object mixin

The policy_container interface class defines a set of functions for managing policies using policy_queue arguments. These functions provide a simple, standard way to implement policies across a verification environemnt.

interface class policy_container;

    // Queries
    pure virtual function bit has_policies();

    // Assignments
    pure virtual function void set_policies(policy_queue policies);
    pure virtual function void add_policies(policy_queue policies);
    pure virtual function void clear_policies();

    // Access
    pure virtual function policy_queue get_policies();

    // Copy
    pure virtual function policy_queue copy_policies();

endclass: policy_container

Figure 16. The policy_container interface class

The policy_object mixin implements the policy_container interface and contains a protected policy_queue for managing policies (a complete example is available in the appendix).

class policy_object #(type BASE=uvm_object) extends BASE implements policy_container;

    protected policy_queue m_policies;
  
    // Queries
    virtual function bit has_policies();
        // returns true/false based on size of m_policies
    endfunction: has_policies

    // Assignments
    virtual function void set_policies(policy_queue policies);
        // sets m_policies to a new queue of policies
    endfunction: set_policies

    virtual function void add_policies(policy_queue policies);
        // adds new policies to m_policies
    endfunction: add_policies

    virtual function void clear_policies();
        // clears m_policies
    endfunction: clear_policies

    // Access
    virtual function policy_queue get_policies();
        // return a handle to m_policies
    endfunction: get_policies

    // Copy
    virtual function policy_queue copy_policies();
        // a copy of m_policies
    endfunction: copy_policies
endclass: policy_object

Figure 17. A policy_object base class implementation

The policy_object mixin can be applied to any class that might benefit from the use of policies, as seen in the following examples.

// Use policy_object for transactions class base_txn extends
policy_object #(uvm_sequence_item);

// Use policy_object for sequences class base_seq #(type
REQ=uvm_sequence_item, RSP=REQ) extends policy_object #(uvm_sequence#(REQ, RSP) );

// Use policy_object for configuration objects class cfg_object extends
policy_object #(uvm_object);

Figure 18. Example classes using the policy_object mixin

Protecting the policy queue enforces loosely coupled code

A subtle but significant additional benefit to using a base policy_object class along with the policy_container API is the ability to mark the container's policy queue as protected and prevent direct access to it.

The original implementation called set_item on each policy during the pre_randomize stage. That was necessary because the policy_queue was public, so there was nothing to prevent callers from adding policies without linking them to the target class.

Using an interface class and making the implementation private means the callers may only set or add policies using the available interface class routines, and because those routines are solely responsible for applying policies, they can also check compatibility (and filter incompatible policies) and call set_item immediately. This can be seen in the example policy_object implementation above, in the usage of the protected function try_add_policy.

By calling set_item when the policy is added to the queue, all policies will be associated with the target item automatically, so there is no need to do it during pre_randomize. This eliminates an easily-overlooked requirement for classes extending policy_object to make sure they call super.pre_randomize() in their local pre_randomize function.

Conclusion

The improvements to the policy package presented in this paper provide a more robust and efficient implementation of policy-based constraints for SystemVerilog. The policy package is now capable of managing constraints across an entire class hierarchy, and the policy definitions are tightly paired with the class they constrain. The use of macros reduces the expense of defining common policies, while still allowing great flexibility in any custom policies necessary.

A functional package is available for download [6] which can be included directly in a project to start using policies immediately.

References

[1]
J. Dickol, SystemVerilog Constraint Layering via Reusable Randomization Policy Classes. DVCon, 2015.
[2]
J. Dickol, Complex Constraints: Unleashing the Power of the VCS Constraint Solver. SNUG Austin, 2016.
[3]
K. Vasconcellos and J. McNeal, Configuration Conundrum: Managing Test Configuration with a Bite-Sized Solution. DVCon, 2021.
[4]
C. McClish, Bi-Directional UVM Agents and Complex Stimulus Generation for UDN and UPF Pins. DVCon, 2021.
[5]
J. Vance, J. Montesano, M. Litterick, and J. Sprott, Be a Sequence Pro to Avoid Bad Con Sequences. DVCon, 2019.
[6]
D. Mills and C. Haldane, policy_pkg Source Code. [Online]. Available: https://github.com/DillanCMills/policy%5C_pkg

Appendix: Source Code

The full policy package implementation is found in the following repository: https://github.com/DillanCMills/policy_pkg