Reduction methods fold an array to one value: sum(), product(), and(), or() and xor(). The result width is the element width.
Summing a byte array wraps at 8 bits. Widen each element first with a with clause, writing q.sum() with (int'(item)) for a byte unsigned array, where item names the current element. A plain byte is signed, so int'(item) sign-extends it: write int'(unsigned'(item)) there or F0 counts as -16.
Expression over {8'hF0, 8'hA0, 8'hF0} | Result |
|---|---|
| 8'h80: accumulation wraps at 8 bits |
| 640: full 32-bit total |
| 3: loop-free count of matching elements |
min(), max() and unique() differ from reductions: they return queues. Capture the queue, check size(), then use element [0]. The order of unique() results is unspecified, so sort them before checking positions.
Locator methods scan against a with expression and always return a queue. An empty queue represents no match, so no sentinel value is ever needed.
find returns matching elements and find_index returns their positions. find_first and find_last stop at one match but still return a queue of zero or one.
sort(), rsort() and reverse() mutate the array in place and return nothing. Arrays of objects (the OOP part) need a with key such as sort(t) with (t.addr), since objects have no natural order.
Three questions pick the container: is the size known at compile time, does the collection grow and shrink during the run, and is the index space dense or sparse.
Fixed | Dynamic | Queue | Associative | |
|---|---|---|---|---|
Size | Compile-time constant |
| Grows per push | Grows per written key |
Access | O(1) | O(1) | O(1) at ends, O(n) middle | Lookup per key, slower than indexed |
Memory | Full footprint always | Exactly n elements | Small per-element overhead | Per-entry overhead, only touched keys |
Use | RTL memories, tables | Payloads sized from config | FIFOs, scoreboards, logs | Sparse memories, ID lookup |
Dynamic grown per iteration with `new[]`: quadratic in practice, so that workload is a queue.
Dense 0..N-1 keys in an associative array: pure hash overhead, so that workload is dynamic.
Per-response `find_first` by ID over a queue: linear each time, so that lookup is associative.
A giant fixed array of which 1% is touched: megabytes wasted, so that model is associative.
sort() after unique() before checking positions?The order of unique() results is unspecified, so a check on positions could pass on one simulator and fail on another. Sorting fixes the order.
find_first matched nothing and the code reads [0] directly. What happens?It indexes an empty queue, which is out of bounds. Check size() before touching [0].
find_first by ID over a queue. What container should replace it?An associative array keyed by ID. Per-response linear search becomes a direct lookup plus delete(id).
payload is a byte unsigned queue holding F0, A0, F0. What does payload.sum() return?8'h80, not 640. The sum is computed at the element width, 8 bits, so the true total 0x280 wraps to 0x80.
Wrapped versus widened sums, conditional counts, finders with miss guards, unique, and in-place sorting.
module tb_array_methods;
byte unsigned payload[$];
int vals[$];
int idxs[$];
int one[$];
int none[$];
byte unsigned mymin[$];
int errors = 0;
initial begin
payload = {8'hF0, 8'hA0, 8'hF0};
if (payload.sum() !== 8'h80) errors++; // 8-bit wrap: F0+A0+F0 = 280 -> 80
if (payload.sum() with (int'(item)) !== 640) errors++;
if (payload.sum() with (int'(item > 8'h80)) !== 3) errors++;
mymin = payload.min(); // min returns a queue
if (mymin.size() != 1 || mymin[0] !== 8'hA0) errors++;
vals = {4, 1, 4, 9, 1};
vals = vals.unique(); // one of each value, order unspecified
vals.sort(); // fix the order -> {1, 4, 9}
if (vals.size() != 3) errors++;
if (vals[0] !== 1 || vals[1] !== 4 || vals[2] !== 9) errors++;
idxs = vals.find_index(x) with (x > 2); // positions {1, 2}
if (idxs.size() != 2 || idxs[0] !== 1 || idxs[1] !== 2) errors++;
one = vals.find_first(x) with (x == 9); // queue of 1
if (one.size() != 1 || one[0] !== 9) errors++;
none = vals.find_first(x) with (x > 100); // queue of 0 = miss
if (none.size() != 0) errors++;
vals.rsort(); // in place -> {9, 4, 1}
if (vals.size() != 3 || vals[0] !== 9 || vals[2] !== 1) errors++;
if (errors != 0) $fatal(1, "FAIL: array methods self-check");
$display("PASS: widths widened, finders guarded, sorting done in place");
$finish;
end
endmodulePASS: widths widened, finders guarded, sorting done in placeRun with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. Icarus 13 cannot run this bench (it does not support the with clause on array methods).