Array methods and with expressions

Search, count and order collections with built-in methods, and keep each result wide enough to hold the answer.

Lesson
2.4
Time
25 min
Runs on
Verilator
Check
5 questions

A watchdog counts reads that have waited more than 1,000 cycles with age.sum() with (item > 1000). Two reads hang, yet a simulator that follows the standard reports 0 stuck reads. The standard gives this sum the 1-bit type of the comparison, so 1 + 1 wraps to 0. Another simulator widens the sum and reports 2, so the same line behaves differently by tool.

Two stuck reads, counted two ways
01234age[ID 3]998999100110021003age[ID 7]10001001100210031004age[ID 9]4041424344sum with (item > 1000)sum with (int'(item > 1000))012two stuck reads; 1-bit sum says 0

The 1-bit sum shows the count modulo 2. The values follow the standard's typing rule.

SystemVerilog arrays and queues have built-in locator methods that search them. A with clause gives the condition, and the name item stands for each element in turn.

find returns the matching elements, and find_index returns their indexes. find_first, find_last and their _index forms return at most one match. min, max and unique need no condition.

Call, with q = {12, 1500, 40, 2048}Result
q.find with (item > 1000){1500, 2048}
q.find_index with (item > 1000){1, 3}
q.find_first_index with (item > 1000){1}
q.find with (item > 5000){} (empty)
q.max(){2048}

Locator methods always return a queue, even for a single answer.

Every locator method returns a queue, because the answer may be empty. Check size() before you read element 0 of the result, or an empty search quietly reads as 0. Searching an empty array also returns an empty queue.

Every locator method leaves the original collection unchanged. Only the reordering methods described below modify the array they are called on.

The methods work on associative arrays too. There, find_index returns keys, so age.find_index with (item > 1000) on int age [int] gives the IDs of the stuck reads directly.

Methods that reduce

Reduction methods combine all the elements into one value: sum, product, and, or and xor. Without a with clause, the result has the element type, so the sum of a byte array wraps at 8 bits.

They are handy beyond counting. The xor of every data word in a burst gives a quick signature to compare against, and or tells you whether any beat set a given bit.

With a with clause, the standard gives the result the type of the with expression. A comparison such as item > 1000 is 1 bit wide, so the sum of the comparisons is 1 bit wide too, and it can only be 0 or 1.

To count matches, cast inside the clause: age.sum() with (int'(item > 1000)). Each match then contributes a 32-bit 1. A cast outside the call comes too late, because the carry bits have already been lost. The same typing rule applies to and, or and xor with a with clause.

Sum the comparison

n = age.sum() with (item > 1000);
// 1-bit result: 2 matches give 0

Reports the count modulo 2 on a simulator that follows the standard.

Sum a widened comparison

n = age.sum() with (int'(item > 1000));
// 32-bit result: 2 matches give 2

Counts correctly on every simulator.

Methods that reorder

sort, rsort, reverse and shuffle rearrange the array itself and return nothing. sort accepts a with clause that gives the sort key, such as q.sort() with (item.id) for a queue of structs.

These methods apply to fixed arrays, dynamic arrays and queues. An associative array cannot be reordered, because its keys already fix the order.

Reordering destroys the arrival order, and arrival order may be exactly what you need to check. Sort a copy instead: after sorted = got; sorted.sort();, the original got still shows the order in which the responses came.

Finding duplicates and naming the iterator

unique returns one copy of each distinct value. If u = ids.unique(); leaves u.size() smaller than ids.size(), some ID appears more than once, which on many buses means two reads were issued with an ID still in use.

The name item is only a default; q.find(x) with (x > 1000) names the iterator x instead. The iterator also knows its own position: item.index is the index of the current element.

That makes order checks short. For a queue of beat numbers, q.find_index with (item != item.index) returns every position holding the wrong beat, so an empty result means the beats arrived in order.

Choosing between a method and a loop

A method states the question in one line, and a reader can check it at a glance. A foreach loop is longer, but it can stop early, report as it goes, or update several counters at once.

Either way, the same rule applies to the answer. Make the variable that holds a count as wide as the largest count you could see, and think about the empty case before you index a result.

Methods also make intent easy to review. pending.find_index with (item > 1000) reads almost like the sentence it implements, whereas a loop has to be read line by line to see what it does.

Try it

find_index on the associative array returned the keys, 7 and 12, which are the IDs of the stuck reads. The cast inside the with clause made the count 2, and the empty search returned an empty queue.

Sorting the copy left the arrival order intact. Change the threshold to 10 and predict every line before you run it.

RUNVerilator · simulator service
Edit the code, then run it
Expected: stuck IDs: 2 found, 7 and 12 stuck count = 2 oldest wait = 2048 cycles reads over 5000 cycles: 0 arrival order 7 3 9, sorted copy 3 7 9

Check yourself

Predict the outputPredict the exact output.
module tb;
  int q [$];
  int r [$];
  initial begin
    q = '{4, 9, 1, 9};
    r = q.find_index with (item == 9);
    $display("%0d %0d %0d", r.size(), r[0], r[1]);
  end
endmodule
ConceptUnder the standard, what type does age.sum() with (item > 1000) return?
Conceptr = q.find with (item > 100); finds no match. What is r afterwards?
Read the waveformOn a simulator that follows the standard, the watchdog computes age.sum() with (item > 1000). What does it report in cycle 3?
Ages of three outstanding reads
0123age[ID 1]80090010001100age[ID 4]950105011501250age[ID 6]10203040

Each age grows by one every cycle a read waits; the trace shows every 100th cycle.

Spot the bugEach option is the body of function automatic int count_over(int q[$], int lim), which must return how many elements are greater than lim. Which option is wrong?

Remember

  • Locator methods such as find and find_index return a queue, which may be empty; check its size.
  • On an associative array, find_index returns keys, such as the IDs of matching reads.
  • With a with clause, a reduction takes the type of the with expression; count with int'(condition).
  • sort, rsort, reverse and shuffle change the array itself; sort a copy to keep the arrival order.
  • Make a count variable as wide as the largest count you could see.

Practice