Sunday, July 6, 2014

The Not So Comprehensive Guide to SystemVerilog Array Constraints

A few weeks back, during a late evening, I was writing some SystemVerilog code that was declaring constraints on arrays. My brain was already powering down and I just wanted to search the net for a code snippet I could quickly copy and adapt. I couldn't find anything, so that inspired me to write this post, to save some snippets for posterity (and for search engines).

The problem I was facing was how to constrain the last element of a dynamic array to have a specific value. I didn't know the exact size of the array, so I tried the naïve approach:

rand int some_dynamic_array[];

constraint last_elem_c {
  some_dynamic_array[some_dynamic_array.size() - 1] == 5;
}

Turns out that using size(), or any other random value, as an index is not allowed. Some programming languages provide a short hand for the last item in the array, e.g. Python allows us to get the last element of a list by doing list[-1]. This isn't possible in SystemVerilog, so what we want to do is going to take a bit more code:

constraint last_elem_c {
  foreach(some_dynamic_array[i])
    if (i == some_dynamic_array.size() - 1)
      some_dynamic_array[i] == 5;
}

Let's take a look at a few more array constraints that could be useful. What we might want is to constrain an array to contain a specific element. This is easily done using the inside operator:

constraint contains_c {
  2 inside { some_dynamic_array };
}

Our array will now contain the value 2, but we don't care at what exact index. The complementary of this is that we want an array to not contain a specific element. This is simply the negation of the above expression:

constraint not_contains_c {
  !(-3 inside { some_dynamic_array });
}

The LRM also shows an example of how to achieve this using the unique operator, but that requires more lines of code and also makes the elements of the array unique. And speaking of unique elements...

Another constraint we may want to place on array is that all of its elements are unique. This can be done using the unique operator:

constraint all_elems_unique_c {
  unique { some_dynamic_array };
}

This is short and sweet, but the unique operator is something that was just recently added in the SystemVerilog 2012 standard. Some of you may want to stay away from it for now (either because of you are using legacy simulators or because you don't want to be early adopters). We can write this constraint in 2009 syntax, but it's going to need a few more lines of code:

constraint all_elems_unique_c {
  foreach (some_dynamic_array[i])
    foreach (some_dynamic_array[j])
      if (i != j)
        some_dynamic_array[i] != some_dynamic_array[j];
}

This old school snippet may remind you of the days when you just started out with programming, when you used double traversal to compare two arrays.

A nifty feature when it comes to array constraints (and not only) is to be able to write multiple constraint expressions under a single foreach (or as in this case if) block:

constraint using_constraint_sets_c {
  foreach (some_dynamic_array[i])
    if (i == 1) {
      some_dynamic_array[i] % 2 == 1;
      some_dynamic_array[i] != 1;
    }
}

The snippet above constrains the second element to be odd, but at the same time to not be 1. The way I was writing this a little while back, before knowing that I can group constraints with brackets, resulted in very long expressions and could potentially result in issues with operator precedence:

constraint using_constraint_sets_c {
  foreach (some_dynamic_array[i])
    if (i == 1)
      some_dynamic_array[i] % 2 == 1 && some_dynamic_array[i] != 1;
}

What we may also want to constrain is that two arrays are equal. If our arrays are packed, then we can use the == operator:

rand bit[9:0][3:0] some_packed_array, some_other_packed_array;

constraint packed_arrays_equal_c {
  some_packed_array == some_other_packed_array;
}

Using the == operator is not possible for unpacked arrays, as they are not integral expressions. In this case we have to explicitly constrain each element via a foreach block:

rand bit[3:0] some_unpacked_array[10], some_other_unpacked_array[10];

constraint unpacked_arrays_equal_c {
  foreach (some_other_unpacked_array[i])
    some_other_unpacked_array[i] == some_unpacked_array[i];
}

Before closing, let's have a look at an example of how we can use some of these constraints together: generating a permutation of a one-dimensional array. For simplicity, I'll assume that the source array's elements are unique.

rand int some_other_dynamic_array[];

// assumes the source array has unique elements
constraint is_a_permutation_c {
  // arrays must have the same size
  some_other_dynamic_array.size() == some_dynamic_array.size();

  // all elements in the source array must be in the destination array
  foreach (some_other_dynamic_array[i])
    some_other_dynamic_array[i] inside { some_dynamic_array };

  // all elements in the destination array must be unique
  unique { some_other_dynamic_array };

  // "inside" operator is bidirectional
  // - if we don't want to change the src array, we have to solve it before
  solve some_dynamic_array before some_other_dynamic_array;
}

The first requirement for a permutation is to be of the same size as the original array. Second, each of the elements in our permutation must be contained inside the source array. This doesn't guarantee, however, that all of the elements of our original array will be present in the "permutation"; e.g. if the source array is { 1, 2, 3 }, then the "permutation" could just as well be { 2, 2, 2 }. Because we assumed that our source array contains only unique elements, then by constraining our "permutation" to also contain only unique elements, we will make sure that each element in the first array will be present in the second array. Because the inside operator is bidirectional, any constraints we add on the permuted array will also potentially propagate to the source array. If we don't want this to happen, we can also force unidirectionality by using a solve ... before ... block.

These are the most interesting array constraints I could think of for the moment. I will gladly update the list if more come up. If you have tried implementing any tricky array constraint, but couldn't quite get it to work, let me know in the comments section. If we put our heads together then maybe we can find a way to solve it.

3 comments:

  1. Thank for the post . really helpful

    ReplyDelete
  2. Explained well by using unique sv2012 construct

    ReplyDelete
  3. Hi Tudor,
    I recently started reading your blog and it is really very informative.
    Can you pls. explain in detail about 'solve before" construct? If I don't use it in the above permutation example, what will exactly happen? I will prefer some more examples on "solve before" since it is sometimes confusing.
    Thanks,
    Ashish

    ReplyDelete