Keeping Your Constraints in One Basket

Doing constrained random verification is pretty easy on paper. You know what the transactions look like and what constraints you want to apply to their fields. It gets trickier when you want to convert this intent into a real life verification environment by building all the sequence items and sequences. If done improperly, this can lead to a lot of doubled up work. A problem I've hit a couple of times is how to handle my constraints so as to avoid scattering them all over the place.

Let's take as an example an AHB UVC. If you're not familiar with the AMBA High Performance Bus protocol, the specification is available here (only for registered ARM customers, but you can also find more info by searching the net).

Here is how a sequence item could look like:

class vgm_ahb_item extends uvm_sequence_item;
rand bit[31:0] addr;
rand vgm_ahb_size_e size;
rand vgm_ahb_burst_e burst;

// other fields (omitted for clarity)
// ...

// address must be aligned to the size of the transfer
constraint aligned_address_c {
size == VGM_AHB_BYTES_2 -> addr[0:0] == 0;
size == VGM_AHB_BYTES_4 -> addr[1:0] == 0;
size == VGM_AHB_BYTES_8 -> addr[2:0] == 0;
size == VGM_AHB_BYTES_16 -> addr[3:0] == 0;
size == VGM_AHB_BYTES_32 -> addr[4:0] == 0;
size == VGM_AHB_BYTES_64 -> addr[5:0] == 0;
size == VGM_AHB_BYTES_128 -> addr[6:0] == 0;
}

// bursts must not cross a 1kB address boundary
constraint dont_cross_1kb_c {
burst inside { VGM_AHB_INCR4, VGM_AHB_INCR8, VGM_AHB_INCR16 }
-> addr[10:0] <= 'h4_00 - vgm_ahb_get_num_transfers(burst) * 2**size;
}
endclass

The specification restricts the values of the addr field based on the values of the size and burst fields. This gets translated into the two constraints: aligned_address_c and  don't_cross_1kb_c.

Now that we have our groundwork laid out, we want to start verifying our DUT. We want to stress it by accessing consecutive address locations with different sizes and burst types. Our sequence will have the burst type, size and the first address location as a fields. An additional field will configure how many address clusters we access. Naturally we want to adhere to the protocol so, for example, starting a 4-byte wide transfer to address 0x431 would be illegal. The constraints we defined for the item have to hold here as well.

You might be tempted to do something like this:

class vgm_ahb_consec_sequence extends uvm_sequence #(vgm_ahb_item);
rand int count;
rand bit[31:0] addr;
rand vgm_ahb_size_e size;
rand vgm_ahb_burst_e burst;

// address must be aligned to the size of the transfer
constraint aligned_address_c {
size == VGM_AHB_BYTES_2 -> addr[0:0] == 0;
size == VGM_AHB_BYTES_4 -> addr[1:0] == 0;
size == VGM_AHB_BYTES_8 -> addr[2:0] == 0;
size == VGM_AHB_BYTES_16 -> addr[3:0] == 0;
size == VGM_AHB_BYTES_32 -> addr[4:0] == 0;
size == VGM_AHB_BYTES_64 -> addr[5:0] == 0;
size == VGM_AHB_BYTES_128 -> addr[6:0] == 0;
}

// bursts must not cross a 1kB address boundary
constraint dont_cross_1kb_c {
burst inside { VGM_AHB_INCR4, VGM_AHB_INCR8, VGM_AHB_INCR16 }
-> addr[10:0] <= 'h4_00 - vgm_ahb_get_num_transfers(burst) * 2**size;
}

// body() definition
// ...
endclass

Copy-pasting is rarely a good idea in software development. It just gives us more places to update our code should our constraints change (for example we want to add more because our initial constraints were not enough). It also creates more places where mistakes can creep in.

Ideally we wouldn't have to do any duplication. Couldn't we somehow make use of the AHB sequence item class? It already defines all the constraints we need...

It turns out we can. We can tweak our previous example by defining a vgm_ahb_item object  as a protected field. By adding constraints to our API fields that tie them to the fields of this field we are "exporting" the sequence item constraints to the sequence. Here's how all this would look like:

class vgm_ahb_consec_sequence extends uvm_sequence #(vgm_ahb_item);
rand int count;
rand bit[31:0] addr;
rand vgm_ahb_size_e size;
rand vgm_ahb_burst_e burst;

// body() definition
// ...

// constraint exporter
protected rand vgm_ahb_item m_item = new();

constraint item_constraints_c {
addr == m_item.addr;
size == m_item.size;
burst == m_item.burst;
}
endclass

The cool thing about this approach is that it can be retrofitted to existing code because there are no modifications to the API. I favor this approach over just defining one field of type vgm_ahb_item and using that for inline constraints. Should our sequence item contain any extra fields (for direction, data, protection, etc.) it would not be clear which ones are actually used in the sequence. By explicitly defining the sequence fields we make it clear what the API is.

A working example that includes the complete code can be found at the blog's SourceForge repository here.

Stay tuned for more SystemVerilog tips and tricks!

Comments