Skip to content

SystemVerilog · Module 3

Array Methods

sort, rsort, find, find_index, sum, min, max, unique — with-clause expressions.

Module 3 · Page 3.5

The Loop You Should Stop Writing Manually

Before array methods existed in SystemVerilog, finding the maximum value in an array required a foreach loop with a running maximum variable — five lines for something that should be one. Sorting required building or importing a sort function. Filtering elements by a condition required another loop with a temporary array.

The array reduction and locator methods introduced in SV-2005 and extended in later revisions eliminate most of that boilerplate. They work uniformly across fixed-size arrays, dynamic arrays, and queues — write the method once, use it on any compatible array type. The return types are consistent: locator methods return queues, in-place methods modify the original array directly.

The key concept that unlocks the full power of these methods is the with clause. arr.sum() sums all elements. arr.sum() with (item.data) sums just the data field of each struct element. arr.find() with (item > threshold) returns all elements greater than a threshold. The item keyword inside the with clause represents the current element — exactly like a lambda expression in other languages.

Three Method Families — Know Which Is Which

Ordering methods (in-place)

sort(), rsort(), shuffle() — modify the array directly. No return value. The original array is reordered after the call.

Locator methods (return queue)

find(), find_first(), find_last(), find_index(), min(), max(), unique(), unique_index() — return a new queue. Original array unchanged.

Reduction methods (return scalar)

sum(), product() — return a single value of the same type as the array element (or as specified by the with clause).

with clause — the power multiplier

method() with (item.field) applies the method to a field of each element, or to a computed expression. item = current array element in the iteration.

The with clause is optional for most methods. Without it, the method operates on the elements directly. With it, the method operates on the expression you define — where item stands for each element as the method iterates. This lets you sort an array of structs by a specific field, find elements matching a computed condition, or sum a single field across all elements.

Complete Method Reference

SystemVerilog — All Array Methods
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int arr [] = '{50, 10, 40, 10, 30, 20, 40};
 
// ════════════════════════════════════════════════════════════════
// ORDERING METHODS — in-place, no return value
// ════════════════════════════════════════════════════════════════
 
arr.sort();          // '{10,10,20,30,40,40,50}  — ascending
arr.rsort();         // '{50,40,40,30,20,10,10}  — descending
arr.shuffle();       // '{??}  — random permutation (uses $urandom)
 
// sort by a field of a struct
typedef struct { int id; int score; } rec_t;
rec_t recs [];
recs.sort() with (item.score);    // sort recs by .score field ascending
recs.rsort() with (item.id);      // sort recs by .id field descending
 
// ════════════════════════════════════════════════════════════════
// LOCATOR METHODS — return queue of matching elements/indices
// ════════════════════════════════════════════════════════════════
 
// find — ALL elements matching condition
int above30 [$]       = arr.find() with (item > 30);        // '{40,40,50}
int eq10    [$]       = arr.find() with (item == 10);       // '{10,10}
 
// find_first / find_last — one element
int first_big [$]     = arr.find_first() with (item > 30);  // '{40}
int last_big  [$]     = arr.find_last()  with (item > 30);  // '{50}
 
// find_index — indices of matching elements
int idx_above30 [$]   = arr.find_index() with (item > 30);  // '{2,4,0}
int first_idx   [$]   = arr.find_first_index() with (item > 30); // '{2}
int last_idx    [$]   = arr.find_last_index()  with (item > 30); // '{0}
 
// min / max — queue containing the single min or max element
int min_val [$]       = arr.min();                           // '{10}
int max_val [$]       = arr.max();                           // '{50}
 
// unique / unique_index — remove duplicate values
int uniq    [$]       = arr.unique();                        // '{50,10,40,30,20}
int uniq_idx[$]       = arr.unique_index();                  // first occurrence indices
 
// ════════════════════════════════════════════════════════════════
// REDUCTION METHODS — return scalar
// ════════════════════════════════════════════════════════════════
 
int total   = arr.sum();                  // 200 (10+10+20+30+40+40+50)
int product = arr.product();              // large number
 
// with clause: sum a specific field
int score_total = recs.sum() with (item.score);   // sum of all .score fields
int cnt_above30 = arr.sum() with (item > 30);     // count elements > 30 (each match contributes 1)
MethodFamilyReturnswith clause?
sort()Orderingvoid (in-place)Optional — sort by expression
rsort()Orderingvoid (in-place)Optional — sort desc by expression
shuffle()Orderingvoid (in-place)Not supported
find()LocatorQueue of elementsRequired — the filter condition
find_first()LocatorQueue (0 or 1 element)Required
find_last()LocatorQueue (0 or 1 element)Required
find_index()LocatorQueue of int indicesRequired
find_first_index()LocatorQueue (0 or 1 int)Required
find_last_index()LocatorQueue (0 or 1 int)Required
min()LocatorQueue (1 element)Optional — compare by field
max()LocatorQueue (1 element)Optional — compare by field
unique()LocatorQueue of unique valuesOptional — unique by field
unique_index()LocatorQueue of int indicesOptional
sum()ReductionScalar (element type)Optional — sum expression
product()ReductionScalar (element type)Optional

Visual — What Each Method Returns

Input Array and Method Results at a Glance

Starting array: int arr [] = '{50, 10, 40, 10, 30, 20, 40}

Method callResultTypeNotes
arr.sort()arr = '{10,10,20,30,40,40,50}void — arr modifiedIn-place ascending
arr.rsort()arr = '{50,40,40,30,20,10,10}void — arr modifiedIn-place descending
arr.min()'{10}Queue of 1Access via [0]
arr.max()'{50}Queue of 1Access via [0]
arr.sum()200Scalar intDirect assignment
arr.find() with (item > 30)'{50,40,40}QueueAll matching elements
arr.find_first() with (item > 30)'{50}Queue of 0 or 1First match only
arr.find_index() with (item == 10)'{1,3}Queue of intIndices, not values
arr.unique()'{50,10,40,30,20}QueueOrder of first occurrence
arr.sum() with (item > 30)3Scalar intCounts elements matching condition

The with Clause — How item Works

ExpressionWhat item isEffect
arr.sum() with (item)Each int elementSum of all elements (same as no-with)
arr.sum() with (int'(item > 30))int (1 if element > 30, else 0)Count of elements greater than 30 — the int' cast is required, see below
recs.sum() with (item.score)The .score field of each structSum of all score fields
recs.sort() with (item.id).id field used as sort keySort records by ID
recs.find() with (item.score > 80)Struct record where score > 80All records with score above 80
arr.max() with (item % 10)item mod 10 — computed expressionElement with largest units digit

Code Examples — Sorting Records to Scoreboard Analysis

Example 1 — Beginner: All Methods on Integer Arrays

Example 1 — Array Method Basics
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module tb_methods_basic;
 
  int arr [] = '{50, 10, 40, 10, 30, 20, 40};
 
  initial begin
 
    // ── Ordering ──────────────────────────────────────────────────
    arr.sort();
    $display("Sorted asc:   %p", arr);        // '{10,10,20,30,40,40,50}
    arr.rsort();
    $display("Sorted desc:  %p", arr);        // '{50,40,40,30,20,10,10}
 
    // Reset for remaining tests
    arr = '{50, 10, 40, 10, 30, 20, 40};
 
    // ── Reduction ─────────────────────────────────────────────────
    $display("sum     = %0d", arr.sum());       // 200
    $display("min     = %0d", arr.min()[0]);  // 10 (access [0] of returned queue)
    $display("max     = %0d", arr.max()[0]);  // 50
 
    // ── Count using sum with condition ────────────────────────────
    $display("count > 30: %0d", arr.sum() with (item > 30));   // 3
    $display("count == 10: %0d", arr.sum() with (item == 10)); // 2
 
    // ── Locators ──────────────────────────────────────────────────
    int big[$] = arr.find() with (item > 30);
    $display("elements > 30: %p", big);        // '{50,40,40}
 
    int first[$] = arr.find_first() with (item > 30);
    $display("first > 30: %0d", first[0]);     // 50
 
    int idx[$] = arr.find_index() with (item == 10);
    $display("indices of 10: %p", idx);        // '{1,3}
 
    // ── Unique ────────────────────────────────────────────────────
    int u[$] = arr.unique();
    $display("unique: %p", u);                 // '{50,10,40,30,20}
 
    // ── No match: find returns empty queue ───────────────────────
    int none[$] = arr.find() with (item > 100);
    $display("find > 100: size=%0d", none.size());  // 0
 
    $finish;
  end
 
endmodule

Expected output:

Simulation Output
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Sorted asc:   '{10, 10, 20, 30, 40, 40, 50}
Sorted desc:  '{50, 40, 40, 30, 20, 10, 10}
sum     = 200
min     = 10
max     = 50
count > 30: 3
count == 10: 2
elements > 30: '{50, 40, 40}
first > 30: 50
indices of 10: '{1, 3}
unique: '{50, 10, 40, 30, 20}
find > 100: size=0

Example 2 — Intermediate: Sorting and Filtering Structs

Example 2 — Methods on Struct Arrays
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module tb_struct_methods;
 
  typedef struct {
    int            tid;
    logic [7:0]  opcode;
    int            latency;    // cycles from send to response
  } txn_t;
 
  txn_t log [];
 
  initial begin
    // Simulate 5 captured transactions with different latencies
    log = new[5];
    log[0] = '{0, 8'h10, 15};
    log[1] = '{1, 8'h20, 3};
    log[2] = '{2, 8'h10, 22};
    log[3] = '{3, 8'h30, 8};
    log[4] = '{4, 8'h20, 5};
 
    // ── Sort by latency ascending ────────────────────────────────
    log.sort() with (item.latency);
    $display("Sorted by latency:");
    foreach (log[i])
      $display("  tid=%0d op=0x%02h lat=%0d", log[i].tid, log[i].opcode, log[i].latency);
 
    // ── Find high-latency transactions ────────────────────────────
    txn_t slow[$] = log.find() with (item.latency > 10);
    $display("High latency (>10): %0d transactions", slow.size());
    foreach (slow[i])
      $display("  tid=%0d lat=%0d", slow[i].tid, slow[i].latency);
 
    // ── Average latency using sum with ────────────────────────────
    int total_lat = log.sum() with (item.latency);
    $display("Average latency = %0d cycles", total_lat / log.size());
 
    // ── Max latency ───────────────────────────────────────────────
    txn_t worst[$] = log.max() with (item.latency);
    $display("Worst: tid=%0d lat=%0d", worst[0].tid, worst[0].latency);
 
    // ── Unique opcodes seen ───────────────────────────────────────
    txn_t uniq_ops[$] = log.unique() with (item.opcode);
    $display("Unique opcodes: %0d", uniq_ops.size());
    foreach (uniq_ops[i])
      $display("  0x%02h", uniq_ops[i].opcode);
 
    $finish;
  end
 
endmodule

Expected output:

Simulation Output
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Sorted by latency:
  tid=1 op=0x20 lat=3
  tid=4 op=0x20 lat=5
  tid=3 op=0x30 lat=8
  tid=0 op=0x10 lat=15
  tid=2 op=0x10 lat=22
High latency (>10): 2 transactions
  tid=0 lat=15
  tid=2 lat=22
Average latency = 10 cycles
Worst: tid=2 lat=22
Unique opcodes: 3
  0x10
  0x20
  0x30

Example 3 — Verification: Coverage Analysis and Scoreboard Summary

Example 3 — End-of-Test Analysis Using Array Methods
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module tb_analysis;
 
  typedef struct {
    int            txn_id;
    logic [7:0]  opcode;
    int            latency;
    bit            passed;
  } result_t;
 
  result_t results [];
 
  initial begin
    results = new[8];
    results[0] = '{0, 8'h10, 5,  1};
    results[1] = '{1, 8'h20, 12, 0};  // FAIL
    results[2] = '{2, 8'h10, 4,  1};
    results[3] = '{3, 8'h30, 18, 0};  // FAIL
    results[4] = '{4, 8'h20, 7,  1};
    results[5] = '{5, 8'h10, 3,  1};
    results[6] = '{6, 8'h30, 9,  1};
    results[7] = '{7, 8'h20, 25, 0};  // FAIL
 
    // ── Pass/fail statistics ─────────────────────────────────────
    int pass_cnt = results.sum() with (item.passed);
    int fail_cnt = results.sum() with (!item.passed);
    $display("PASS=%0d  FAIL=%0d", pass_cnt, fail_cnt);
 
    // ── Worst-case latency analysis ───────────────────────────────
    result_t slow_q[$] = results.max() with (item.latency);
    $display("Peak latency: tid=%0d lat=%0d cycles",
             slow_q[0].txn_id, slow_q[0].latency);
 
    // ── Find failing transactions and sort by latency ─────────────
    result_t fails[$] = results.find() with (!item.passed);
    fails.sort() with (item.latency);
    $display("Failed transactions (sorted by latency):");
    foreach (fails[i])
      $display("  tid=%0d op=0x%02h lat=%0d",
               fails[i].txn_id, fails[i].opcode, fails[i].latency);
 
    // ── Unique opcodes that failed ────────────────────────────────
    result_t fail_ops[$] = fails.unique() with (item.opcode);
    $display("Distinct failing opcodes: %0d", fail_ops.size());
 
    // ── Average latency of passing transactions only ──────────────
    result_t passing[$] = results.find() with (item.passed);
    int avg = passing.sum() with (item.latency) / passing.size();
    $display("Avg passing latency = %0d cycles", avg);
 
    $finish;
  end
 
endmodule

Expected output:

Simulation Output
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
PASS=5  FAIL=3
Peak latency: tid=7 lat=25 cycles
Failed transactions (sorted by latency):
  tid=1 op=0x20 lat=12
  tid=3 op=0x30 lat=18
  tid=7 op=0x20 lat=25
Distinct failing opcodes: 2
Avg passing latency = 5 cycles

Example 4 — Corner Case: Empty Array, sort on Queue, product Overflow

Example 4 — Edge Cases
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module tb_method_corners;
 
  int arr [];
  int q   [$];
 
  initial begin
 
    // ── Empty array: locators return empty queue ──────────────────
    arr = new[0];
    int r[$] = arr.find() with (item > 0);
    $display("find on empty: size=%0d", r.size());   // 0
    $display("sum  on empty: %0d",      arr.sum());  // 0
    // min/max on empty: returns empty queue — guard [0] access!
    int m[$] = arr.min();
    if (m.size() > 0) $display("min = %0d", m[0]);
    else              $display("min: array empty");        // prints this
 
    // ── sort() works on queues too ────────────────────────────────
    q = '{5, 1, 4, 2, 3};
    q.sort();
    $display("sorted queue: %p", q);     // '{1,2,3,4,5}
 
    // ── sum with int overflow ────────────────────────────────────
    // sum() returns same type as element — for 'int' that's 32-bit signed
    // Large arrays can overflow. Cast to longint for large sums:
    arr = new[4]('{2000000000, 2000000000, 1, 1});
    int     sum_int  = arr.sum();                      // OVERFLOW: wraps at 32-bit
    longint sum_long = arr.sum() with (longint'(item)); // CORRECT: widened
    $display("sum int    = %0d", sum_int);              // wrong (overflowed)
    $display("sum longint= %0d", sum_long);            // 4000000002
 
    // ── unique preserves first-occurrence order ──────────────────
    arr = '{3, 1, 2, 1, 3, 4};
    int u[$] = arr.unique();
    $display("unique order: %p", u);   // '{3,1,2,4} — first occurrence order
 
    $finish;
  end
 
endmodule

Simulation Behavior — What to Know Before Using These

Return Type Rules

Locator methods always return a queue, even when you know logically only one result is possible. min() returns a 1-element queue, not a scalar. find_first() returns a 0-or-1-element queue. Always access the result via [0] and check size before accessing on methods that might return empty queues.

sort() Is Stable in Most Simulators

When two elements compare equal in a sort(), most simulator implementations preserve their relative order — this is called a stable sort. The LRM does not mandate stability, so you should not rely on it for correctness in portable code. If relative order of equal elements matters, add a secondary sort key using a more complex with expression: arr.sort() with ({item.priority, item.id}).

MethodOn empty arrayReturn value
sort(), rsort()No-op, no errorvoid
sum(), product()Returns 0 (neutral element)0
find(), find_index()Returns empty queue'{}
min(), max()Returns empty queue — guard [0] access'{}
unique()Returns empty queue'{}

Where Array Methods Save the Most Code in Verification

Verification Patterns Using Array Methods
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 1. SCOREBOARD: check all-pass with one expression ─────────────
bit all_passed = (results.sum() with (item.passed) == results.size());
if (!all_passed) $error("Not all transactions passed");
 
// ── 2. COVERAGE: verify all expected opcodes were exercised ────────
logic [7:0] seen_ops[];
logic [7:0] expected_ops [] = '{8'h10, 8'h20, 8'h30};
// ... capture seen_ops during simulation ...
logic [7:0] unique_seen[$] = seen_ops.unique();
if (unique_seen.size() < expected_ops.size())
  $error("Coverage gap: only %0d/%0d opcodes exercised",
         unique_seen.size(), expected_ops.size());
 
// ── 3. LATENCY ANALYSIS: find SLA violations ──────────────────────
parameter int SLA_CYCLES = 10;
result_t violations[$] = results.find() with (item.latency > SLA_CYCLES);
int      violation_pct = violations.size() * 100 / results.size();
$display("SLA violations: %0d%% of transactions", violation_pct);
 
// ── 4. SORT TRANSACTIONS FOR DETERMINISTIC REPORT ─────────────────
// Sort by opcode first, then by transaction ID within same opcode
results.sort() with ({item.opcode, item.txn_id});   // compound sort key
 
// ── 5. FIND DUPLICATE TRANSACTION IDS (should never happen) ───────
int all_ids  [$] = results.find_index() with (1);    // all indices (always true)
int uniq_ids [$] = results.unique_index() with (item.txn_id);
if (all_ids.size() != uniq_ids.size())
  $error("Duplicate TIDs detected!");
 
// ── 6. FIND FIRST ERROR AND STOP FURTHER CHECKING ─────────────────
result_t first_fail[$] = results.find_first() with (!item.passed);
if (first_fail.size() > 0)
  $display("First failure: tid=%0d op=0x%02h",
           first_fail[0].txn_id, first_fail[0].opcode);

Bugs Engineers Hit With Array Methods

Bug 1 — Treating min()/max() Result as Scalar

Bug 1 — Direct Scalar Assignment from min()/max()
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int arr [] = '{30, 10, 50, 20};
 
// BUGGY: min() returns a QUEUE, not a scalar
int m = arr.min();        // type mismatch — may compile but gives wrong value
$display("m = %0d", m);    // undefined behavior — NOT necessarily 10
 
// FIXED: index into the returned queue
int min_q[$] = arr.min();
if (min_q.size() > 0)
  $display("min = %0d", min_q[0]);   // 10
 
// Or in one expression (safe only when array is guaranteed non-empty):
$display("min = %0d", arr.min()[0]);  // 10 — clean if array never empty

Bug 2 — sort() Modifies Original — Don't Sort What You Still Need

Bug 2 — In-Place Sort Destroys Original Order
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int log [] = '{50, 10, 30};  // original arrival order matters
 
// BUGGY: sort() is in-place — arrival order is lost!
log.sort();
$display("first arrived: %0d", log[0]);  // 10 — WRONG: was 50
 
// CORRECT: work on a COPY for analysis; preserve original
int sorted [] = log;    // deep copy
sorted.sort();           // sort the copy — original log unchanged
$display("first arrived: %0d", log[0]);    // 50 — correct
$display("sorted min:    %0d", sorted[0]);  // 10 — correct

Bug 3 — sum() Overflow With Large Values

Bug 3 — sum() Result Type Overflow
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int counters [] = '{1500000000, 1500000000, 500000000};
 
// BUGGY: sum() returns same type as element = int (32-bit signed)
// 1.5B + 1.5B = 3B which overflows int32 (max ~2.1B)
int total = counters.sum();
$display("total = %0d", total);   // wrong — overflowed to negative
 
// FIXED: cast each element to a wider type in the with clause
longint safe_total = counters.sum() with (longint'(item));
$display("total = %0d", safe_total);  // 3500000000 — correct

Bug 4 — find() Returns Elements, find_index() Returns Indices

Bug 4 — Confusing find() With find_index()
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int arr [] = '{50, 10, 40, 30};
 
// find() returns VALUES matching the condition
int vals[$] = arr.find() with (item > 30);
$display("values > 30: %p", vals);     // '{50, 40} — the values themselves
 
// find_index() returns INDICES of matching elements
int idxs[$] = arr.find_index() with (item > 30);
$display("indices > 30: %p", idxs);   // '{0, 2} — indices 0 and 2
$display("arr[%0d] = %0d", idxs[0], arr[idxs[0]]); // arr[0] = 50
 
// BUGGY: using find() when you actually need the index
int wrong_idx = vals[0];        // vals[0]=50 — this is a VALUE, not an index!
int val_at_wrong = arr[50];     // FATAL: index 50 out of bounds on 4-element array
Locator methods return a queue, ordering methods modify in place and return nothing, and reduction methods return a scalar whose type follows the with expressionSource arrayfixed, dynamic, or queueLocator: find / min /unique→ QUEUE, empty if no matchOrdering: sort / rsort /reverse→ nothing; modifies IN PLACEReduction: sum / product /xor→ scalar typed by the withexpressionIndex it, and guard foremptyint m = arr.min()[0];Original array ischangedcopy first if you still needitCast the with expressionsum() with (int'(item > x))12
Figure 1 — the three method families and what each one gives back. Locator methods (find, find_index, find_first, min, max, unique) return a QUEUE of results, even when there is exactly one match and even when there are none — an empty queue is how 'no match' is represented, which is why the result must be indexed and guarded rather than assigned to a scalar. Ordering methods (sort, rsort, reverse, shuffle) return nothing at all: they modify the original array in place, so assigning their result does not compile. Reduction methods (sum, product, and, or, xor) return a single value whose TYPE follows the with expression — so a comparison inside the with clause makes the accumulation one bit wide, which is the counting trap described below. Knowing which family a method belongs to answers both 'how do I read the result' and 'did it change my array'.

The Two Result-Type Traps

Both families of method return something narrower than people expect, and both mistakes compile cleanly.

Locator methods return a queue, never a scalar. arr.min() gives a queue containing one element, not an int. int m = arr.min(); is a type mismatch; int m = arr.min()[0]; is the form — and it needs an empty-array guard, because the queue form exists precisely so that an empty array can return an empty queue rather than an undefined value.

A reduction's result type follows its with expression. This is the sharper trap. arr.sum() with (item > 30) looks like it counts matching elements, and the comparison yields a 1-bit value, so the sum is computed in one bit and wraps: two matches give 0, three give 1. The count is not merely wrong, it is approximately random with respect to the answer you wanted.

The fix is to widen the expression explicitly:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int n_big = arr.sum() with (int'(item > 30));   // ✓ 32-bit accumulation

The same reasoning covers the plain form. arr.sum() on a byte array accumulates in 8 bits; widen with arr.sum() with (int'(item)) when the total can exceed the element type.

array_method_types_proof.sv — what the methods actually return
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module array_method_types_proof;
  int  arr [] = '{10, 25, 40, 40, 50, 5};
  byte small [] = '{100, 100, 100};
  int fails = 0;
 
  task automatic expect (input string what, input bit cond);
    if (cond) $display("  PASS  %s", what);
    else begin $display("  FAIL  %s", what); fails++; end
  endtask
 
  initial begin
    $display("\n1. Locator methods return QUEUES");
    begin
      int mn [$] = arr.min();
      int fnd[$] = arr.find() with (item > 30);
      int idx[$] = arr.find_index() with (item == 40);
      $display("  arr.min()      -> queue of %0d, value %0d", mn.size(),  mn[0]);
      $display("  find(>30)      -> queue of %0d", fnd.size());
      $display("  find_index(40) -> indices %p", idx);
      expect("min() returns a 1-element queue", mn.size() == 1);
      expect("find() returns every match",      fnd.size() == 3);
      expect("find_index() returns positions",  idx == '{2, 3});
    end
 
    $display("\n2. An empty array returns an EMPTY queue, not an error");
    begin
      int empty [];
      int mn [$] = empty.min();
      expect("min() of an empty array is an empty queue", mn.size() == 0);
      // mn[0] here would be an out-of-bounds read. Guard before indexing.
    end
 
    $display("\n3. The 1-bit sum trap");
    begin
      // The with expression is a comparison, so it is 1 bit wide, and the
      // sum accumulates in 1 bit. Three matches wrap to 1.
      bit  bad  = arr.sum() with (item > 30);
      int  good = arr.sum() with (int'(item > 30));
      $display("  sum with (item > 30)        = %0d   <- wrapped", bad);
      $display("  sum with (int'(item > 30))  = %0d   <- correct", good);
      expect("uncast boolean sum wraps in 1 bit", bad  == 1);
      expect("int'-cast sum gives the real count", good == 3);
    end
 
    $display("\n4. The same rule for the plain form");
    begin
      byte bad_total = small.sum();               // 300 does not fit in a byte
      int  ok_total  = small.sum() with (int'(item));
      $display("  byte sum = %0d (wrapped), int sum = %0d", bad_total, ok_total);
      expect("byte accumulation wraps",  bad_total != 300);
      expect("widened accumulation is right", ok_total == 300);
    end
 
    $display("\n%0s (%0d failures)\n",
             fails == 0 ? "ALL CHECKS PASSED" : "CHECKS FAILED", fails);
    if (fails) $fatal(1, "array_method_types_proof failed");
    $finish;
  end
endmodule
1

A coverage gate passed on a run that exercised two opcodes out of sixteen

ONE-BIT-REDUCTION-SUM
Symptom

An end-of-test check counted how many distinct opcodes a random test had exercised and failed the run if the count fell below a threshold. It never failed. Reviewing coverage separately showed runs that had exercised as few as two opcodes out of sixteen sailing through a gate that required at least eight.

The gate had been in place for four months and had passed on every run, which is what made it credible and what made nobody look at it.

Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int seen_count [16];        // per-opcode hit counts, filled by the monitor
 
function void check_opcode_coverage();
  // Intent: count how many opcodes were hit at least once.
  bit n_hit = seen_count.sum() with (item > 0);      // ✗ 1-bit accumulation
 
  if (n_hit < 8)
    `uvm_error("COV", $sformatf("only %0d opcodes exercised", n_hit))
endfunction
Diagnostic Evidence

Printing the value the gate was comparing settled it immediately:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  opcodes actually hit : 2
  n_hit (as computed)  : 0
  gate condition       : (0 < 8) -> TRUE ... but no error was reported?

The second surprise explained the first. n_hit is declared bit, so n_hit < 8 compares a 1-bit value against 8 — and the comparison is performed at the width of the larger operand, giving 0 < 8, which is true. So the error should have fired.

It did not, because on most runs n_hit computed to 1 rather than 0: with an odd number of opcodes hit, the 1-bit sum wraps to 1, and 1 < 8 is also true. Adding the print showed the gate firing on some runs and not others, at which point the real behaviour became clear — the value being tested had no relationship to the quantity it was supposed to represent. It was the parity of the opcode count.

Root Cause

The result type of a reduction with a with clause follows the type of the expression. item > 0 is a comparison, so it is one bit wide, and sum() accumulates in one bit — which is addition modulo 2. The result is the parity of the number of matching elements, not the count.

Two coincidences kept it hidden. Declaring the target as bit meant no truncation warning at the assignment, because nothing was being truncated — the expression really was 1 bit. And a parity value of 0 or 1 is always less than 8, so the gate's condition was in fact satisfied on every run; what varied was whether the surrounding logic treated the result as meaningful.

The deeper issue is that this gate belongs to the class of checks that can only pass. It compared a quantity that was never the count against a threshold, and no stimulus could have made it report correctly. Four months of green results were not evidence of coverage; they were evidence that the check was inert.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
function void check_opcode_coverage();
  // Widen the with expression so the accumulation happens in 32 bits.
  int n_hit = seen_count.sum() with (int'(item > 0));
 
  if (n_hit < 8)
    `uvm_error("COV", $sformatf("only %0d of %0d opcodes exercised",
                                n_hit, seen_count.size()))
endfunction

Verifying the fix takes a self-check rather than another run of the gate: build a known array with a known number of matches and assert the count. Three matches must return 3, not 1 — which is the check in the runnable proof above, and it fails against the old expression.

Two habits follow.

Cast the with expression whenever it is a comparison or a narrow field. int'(item > x) for counting, int'(item.field) for summing a narrow struct member. The cast costs nothing and removes the entire class.

Give any threshold check a positive control. A gate that has never failed is indistinguishable from a gate that cannot fail, and the cheapest way to tell them apart is to feed it a value that must trip it — once, deliberately, as part of bring-up. The same reasoning appears in assertion-based verification, where a property that never fires and a property that cannot fail produce identical reports.

For the width and sign rules the accumulation follows, see 4-state versus 2-state types; for the queues these locator methods return, see queues and dynamic arrays.

Interview Questions

A queue containing one element, not a scalar. int m = arr.min(); is a type mismatch; the form is int m = arr.min()[0];.

The queue is not an accident of the API. It exists so that an empty array has something meaningful to return — an empty queue — rather than an undefined scalar. That is also why indexing straight into the result is unsafe: arr.min()[0] on an empty array is an out-of-bounds read, so guard with if (arr.size() > 0) or capture the queue and check its size first.

The whole locator family behaves this way: min, max, find, find_first, find_last, find_index, unique, unique_index all return queues. find_first() returning a queue of at most one element surprises people who expect a scalar, and it is the same reasoning — the "no match" case needs a representation.

find() returns a queue of the matching elements; find_index() returns a queue of their positions.

Which you want depends on what you do next. If you only need the values — summing them, printing them, checking how many there are — find() is direct. If you need to modify the original array, or to correlate with a parallel array, you need the indices, because the elements returned by find() are copies and writing to them does not touch the original.

The _first and _last variants of both exist and still return queues, of at most one element. arr.find_first_index() with (item > 30) is the usual way to ask "where is the first element above the threshold", and it returns an empty queue rather than a sentinel value when nothing matches — which is safer than returning -1 and much easier to get wrong if you forget to check size().

Use arr.sum() with (int'(item > threshold)). The int' cast is not optional.

The obvious form, arr.sum() with (item > threshold), is wrong in a way that compiles silently. The result type of a reduction follows the type of its with expression, and a comparison is one bit wide — so the sum accumulates modulo 2 and returns the parity of the match count rather than the count. Two matches give 0, three give 1.

Nothing warns, because nothing is being truncated: the expression genuinely is 1 bit. If the result is assigned to a bit, there is not even a width mismatch to notice.

Casting the expression to int makes the accumulation 32-bit and the answer correct. The same rule applies to the plain form: byte_arr.sum() accumulates in 8 bits, so widen with sum() with (int'(item)) whenever the total can exceed the element type.

The alternative, arr.find(...).size(), is correct and allocates an intermediate queue — fine for a one-off check, wasteful in a hot loop.

By one field: arr.sort() with (item.score); for ascending, arr.rsort() with (item.score); for descending. Both operate in place and return nothing — a common mistake is writing sorted = arr.sort(), which does not compile.

For a compound key, concatenate the fields into a single packed value so they sort lexicographically: arr.sort() with ({item.priority, item.id});. Priority is the more significant field, so it dominates, and id breaks ties within equal priorities. The fields must be packed types for the concatenation to be meaningful, and the field order in the braces is the sort precedence.

Two cautions. The sort is not specified to be stable, so equal keys may not retain their original relative order — if that matters, include a tiebreaker in the key rather than relying on it. And because these methods modify the original, sorting an array you also need in arrival order requires copying it first.

Reduce both sides to sorted unique sets and compare them directly.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int seen_u [$] = seen_ops.unique();   // queue of distinct values
int exp_u  [$] = expected_ops;
seen_u.sort();
exp_u.sort();
if (seen_u != exp_u)
  `uvm_error("COV", $sformatf("opcode set mismatch: saw %p, expected %p",
                              seen_u, exp_u))

Note that there is no auto in SystemVerilog — the result of unique() is a queue and must be declared as one. Queue comparison with != is element-wise, so this single check catches both a missing opcode and an unexpected one, which is what "exactly the expected set" requires.

If you want the two failures reported separately — usually more useful, because a missing opcode is a stimulus gap and an unexpected one is a decode bug — take the differences explicitly:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int missing [$] = exp_u.find()  with (!(item inside {seen_u}));
int extra   [$] = seen_u.find() with (!(item inside {exp_u}));

The inside operator accepts a queue as its set, which keeps this readable without a nested loop.

Effectively none of them. The locator and reduction methods are testbench constructs: they return queues, which are dynamically sized, and dynamic sizing has no hardware meaning.

That is not a limitation so much as a category distinction. arr.find() with (...) asks a question whose answer size is not known until run time, and hardware has to be built for a fixed maximum. The synthesisable equivalent of a find is a comparator tree with a fixed width and an explicit "how many results can I hold" decision — which is exactly the decision the method hides.

Where they earn their place is everything around the DUT: scoreboards filtering transactions, coverage models counting distinct values, monitors sorting observed items, end-of-test checks comparing sets. In that context they replace loops that are easy to write wrongly, and the code reads as the intent rather than the mechanism.

The one habit to carry across the boundary is the type discipline. The same int'() cast that fixes a reduction in a testbench is the same width reasoning that governs an RTL accumulator — the language just warns you about it less in the testbench.

Where This Is Specified

  • IEEE 1800-2023 §7.12 — Array manipulation methods. The three families and their return types: locator methods return a queue of results, ordering methods operate in place, and reduction methods return a single value.
  • IEEE 1800-2023 §7.12.1 — Array locator methods. find, find_index, find_first, find_last, min, max, unique, unique_index, and the rule that they return an empty queue when nothing matches.
  • IEEE 1800-2023 §7.12.3 — Array reduction methods. sum, product, and, or, xor, and the rule that the result type follows the with expression — the basis of the 1-bit accumulation trap above.
  • IEEE 1800-2023 §7.12.2 — Array ordering methods. sort, rsort, reverse, shuffle, their in-place semantics, and the with clause used to supply a sort key.
  • IEEE 1800-2023 §7.12.4 — Iterator index querying. The item and index names available inside a with clause.
  • IEEE 1800-2023 §11.4.13 — inside operator. Set membership against an array or queue, used by the set-difference form above.

Best Practices and Coding Guidelines

Guard min()/max() on empty arrays

These return empty queues on empty arrays. Always check .size() > 0 before accessing [0], or validate the array is non-empty before calling.

Copy before sort if order matters

sort() and rsort() modify in-place. If you need the original order for any subsequent operation, copy first: auto copy = arr; copy.sort();

Cast to longint for large sums

For int arrays with large values: arr.sum() with (longint'(item)). The result type of sum() defaults to the element type — 32-bit int overflows silently.

Use sum() for counting, find() for values

Counting matches: arr.sum() with (condition) — no queue allocation. Getting values: arr.find() with (condition) — returns the actual elements. Use the right tool for the task.

TaskOne-linerAvoid
Find minimum valuearr.min()[0] (guard for empty)arr.min() assigned to scalar
Count matching elementsarr.sum() with (item > x)arr.find() with (...).size() — allocates queue
Sort struct by field descarr.rsort() with (item.field)Manual bubble sort loop
Get unique valuesarr.unique()Manual dedup loop with associative array
Check all-passarr.sum() with (item.ok) == arr.size()foreach loop with flag variable
First failurearr.find_first() with (!item.ok)Loop with break — more code, same result

Summary — Chapter 3 Complete

Array methods turn verification boilerplate into readable one-liners. The with clause is the key that unlocks them for real-world use — sort by any field, filter by any condition, sum any sub-expression. The three rules that prevent the most bugs: locator methods return queues (index with [0]), ordering methods modify in-place (copy first if you need the original), and sum() inherits the element type (cast to longint for large values).

  • Three families: ordering (in-place), locator (return queue), reduction (return scalar). Know which family each method belongs to before using it.
  • with (item) is a lambda. item = current element. Use it to operate on fields, compute expressions, or define filter conditions.
  • All locator methods return queues — even when only one result is possible. Always use [0] to get the scalar value, and guard against empty results.
  • sort() is in-place. If you need the original order after sorting, work on a copy.
  • sum() with (condition) counts matches. More efficient than find().size() for counting-only tasks.

Part of SystemVerilog Fundamentals·Arrays·Lesson 18 of 53

View program

Continue learning