Part 1 · Language Foundations · Intermediate

Array Reduction & Locator Methods

sum/product/min/max, find variants with `with` clauses, sort/rsort/shuffle, unique, and why locators return queues.

Reduction methods — fold the array to one value

Every unpacked array family (fixed, dynamic, queue, associative) supports built-in reduction methods : sum(), product(), and(), or(), xor(). A crucial semantic detail: the result width is the element width , so summing a byte array wraps at 8 bits unless you widen with a with clause such as q.sum() with (int'(item)). The item keyword names the current element inside the clause — this width trap is a favorite interview gotcha.

systemverilog
module reduction_demo;
  byte payload[$] = {8'hF0, 8'hF0, 8'hF0};

  initial begin
    // WRONG width: 8-bit accumulation wraps (F0+F0+F0 = 2D0 -> D0)
    $display("8-bit sum  = %0h", payload.sum());

    // Correct: widen each element before accumulating
    $display("32-bit sum = %0h", payload.sum() with (int'(item)));

    // Conditional reduction: count elements matching a predicate
    $display("count > 8'h80 = %0d",
             payload.sum() with (int'(item > 8'h80)));

    $display("min=%0p max=%0p", payload.min(), payload.max());
  end
endmodule

Locator methods — search without loops

Locator methods scan the array against a with expression and always return a queue , even when at most one element can match. Why a queue? Because zero matches must be representable — an empty queue — without inventing a sentinel value. So the idiom is: capture the result, check size(), then use element [0]. find returns matching elements, find_index their positions, find_first/find_last stop at one match, and min/max/unique follow the same queue-return convention.

systemverilog
class txn;
  rand int  addr;
  rand bit  is_err;
endclass

txn  log_q[$];
txn  errs[$];
int  idxs[$];
int  vals[$];

initial begin
  // ... log_q filled by monitor ...

  errs = log_q.find(t) with (t.is_err);            // all error txns
  idxs = log_q.find_index(t) with (t.addr > 'h1000); // their positions
  errs = log_q.find_first(t) with (t.is_err);      // queue of 0 or 1

  if (errs.size() > 0)
    $display("first error at addr %0h", errs[0].addr);
  else
    $display("no errors logged");

  vals = {4, 1, 4, 9, 1};
  vals = vals.unique();      // {4, 1, 9} - first occurrences kept
end
diagram
LOCATOR METHODS — ALWAYS RETURN A QUEUE

  log_q: ┌────┬────┬────┬────┬────┐
         │ ok │ERR │ ok │ERR │ ok │
         └────┴────┴────┴────┴────┘
            0    1    2    3    4

  find(t) with (t.is_err)        ──► { e1, e3 }      (elements)
  find_index(t) with (t.is_err)  ──► { 1, 3 }        (positions)
  find_first(t) with (t.is_err)  ──► { e1 }          (size 1)
  find_first(t) with (t.addr<0)  ──► {  }            (size 0 = miss)
                                        │
                                        ▼
                       always check size() before using [0]

Ordering methods — sort, rsort, shuffle, reverse

The ordering methods mutate the array in place and return nothing: sort() ascending, rsort() descending, reverse() flips order, and shuffle() randomizes order (note: shuffle is not controlled by the constraint solver's seed stability rules the way randomize is, so don't lean on it for reproducible stimulus ordering across simulators). For object arrays you must supply a sort key with with, since class handles have no natural order.

systemverilog
txn pending[$];

initial begin
  // Sort transactions by address, then walk in order
  pending.sort(t) with (t.addr);
  foreach (pending[i])
    $display("addr %0h", pending[i].addr);

  pending.rsort(t) with (t.addr);   // descending
  pending.shuffle();                 // random order, in place
end

Key takeaways

  • Reduction width = element width — widen with sum() with (int'(item)) or results wrap.

  • Locators always return queues so 'no match' is an empty queue — check size() before [0].

  • sum() with (int'(predicate)) is the idiomatic loop-free way to count matching elements.

  • sort/rsort/shuffle mutate in place; object arrays need a with clause for the sort key.

Common pitfalls

  • byte_q.sum() into an int and wondering why totals are wrong — accumulation already wrapped at 8 bits.

  • Using find_first(...)[0] directly — empty-queue indexing when nothing matches.

  • Expecting sort() to return the sorted array — it mutates in place and returns void.

  • Sorting class-handle arrays without a with clause — there is no default ordering for objects.