Skip to content

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
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 (item > 30)Boolean: 1 if element > 30, else 0Count of elements greater than 30
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
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
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
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
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
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
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
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
// ── 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()
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
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
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()
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

Interview Questions

Beginner Level

Q1: What does arr.min() return, and how do you get the actual minimum value? A queue containing one element. To get the scalar value: int m = arr.min()[0]; The queue form exists to handle the edge case of an empty array (which returns an empty queue). Directly writing int m = arr.min() is a type mismatch. Always index with [0] after guarding against empty arrays. Q2: What is the difference between find() and find_index()?find() returns a queue of the values that match the condition — the actual array elements. find_index() returns a queue of the integer indices at which matches occur. If you need to know where in the array something is, use find_index(). If you just need the matching values, use find().

Intermediate Level

Q3: How do you count the number of elements in an array that are greater than a threshold without using a loop? Use arr.sum() with (item > threshold). The boolean expression item > threshold evaluates to 1 for each matching element and 0 otherwise. Summing these 1s and 0s gives the count. This is more efficient than arr.find() with (...).size() because no intermediate queue is allocated. Q4: How do you sort an array of structs by a specific field, descending? Use arr.rsort() with (item.field_name). The with clause specifies the sort key, and rsort() gives descending order. For compound sort keys (primary + secondary): arr.sort() with ({item.priority, item.id}) — this concatenates the fields as a packed value and sorts lexicographically by priority first, then id within equal priorities.

Experienced Engineer Level

Q5: At end-of-test, you want to verify that exactly the expected set of opcodes was exercised — not more, not fewer. How would you implement this check using array methods? Collect all seen opcodes into an array from your monitor. Then:

  1. Get unique seen opcodes: auto seen_u = seen_ops.unique() with (item)
  2. Sort both: seen_u.sort(); expected_ops.sort();
  3. Compare: if (seen_u != expected_ops) $error(...); This checks exact match — any missing opcode OR any unexpected opcode causes the comparison to fail. Alternatively, check that every expected opcode appears in seen_u using foreach (expected_ops[i]) assert(seen_u.find_first_index() with (item == expected_ops[i]).size() > 0).

Best Practices & 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.