Fixed and dynamic arrays
Size an array from the transaction it holds, grow it without losing data, and know what happens at the edges of its bounds.
- Lesson
- 2.1
- Time
- 20 min
- Runs on
- Your browser
- Check
- 5 questions
A read scoreboard keeps the expected data of each burst in logic [31:0] exp_beat [16], because the first version of the spec allowed 16 beats. When the test starts issuing 32-beat bursts, the writes to beats 16 to 31 are silently ignored. The checker loops with foreach (exp_beat[i]), so it compares only the first 16 beats of every burst and reports a pass. A DUT bug that corrupts the second half of long bursts goes unnoticed for a month.
Beats 16 and above have no element to land in, so the checker never looks at them.
Fixed arrays have a size set at compile time
A fixed-size array gets its number of elements from the declaration, before the simulation starts. logic [31:0] exp_beat [16]; always has exactly 16 elements, numbered 0 to 15.
Fixed arrays suit counts that the spec fixes for good: 4 byte lanes, 8 DMA channels or 32 interrupt lines. $size(exp_beat) returns the count, which is safer than repeating the number 16 in a loop.
Going past the end does not stop the simulation. The standard says a write to an index outside the array is ignored, and a read returns the element type's default value, which is x for logic and 0 for int. A burst longer than the array therefore loses data without a single message.
Dynamic arrays are sized at run time
A dynamic array is declared with empty brackets, as in logic [31:0] exp_beat [];. It starts with no elements at all, so exp_beat.size() returns 0 until you allocate it.
The constructor new[n] allocates n elements, each set to the element type's default value. For a read burst, the request tells you the size: on an AXI-style bus the length field ARLEN holds the number of beats minus 1, so you allocate new[arlen + 1].
| Statement | Size afterwards | Contents |
|---|---|---|
exp_beat = new[4]; | 4 | four x elements |
exp_beat = new[6](exp_beat); | 6 | the old 4 elements, then 2 x elements |
exp_beat = new[2](exp_beat); | 2 | the first 2 old elements |
exp_beat = new[3]; | 3 | three x elements; the old data is gone |
exp_beat.delete(); | 0 | nothing |
Allocating, growing, shrinking and emptying logic [31:0] exp_beat [].
Once allocated, a dynamic array behaves like a fixed one. You index it, loop over it with foreach, and size() tells you how many elements it holds.
Growing without losing data
The second form of the constructor, new[n](old), allocates n elements and copies in as many old elements as fit. Without the argument in parentheses, the new array starts from default values and the old contents are lost.
The two forms differ by a few characters, so the mistake is easy to make and hard to see. A monitor that appends each beat with new[size() + 1] keeps only the last beat it received, and every earlier beat reads as x.
Grow without copying
// append one beat
exp_beat = new[exp_beat.size() + 1];
exp_beat[exp_beat.size() - 1] = data;
Each append throws away the beats collected so far.
Grow and copy
// append one beat
exp_beat = new[exp_beat.size() + 1](exp_beat);
exp_beat[exp_beat.size() - 1] = data;
Keeps every earlier beat and adds the new one at the end.
Growing one element at a time copies the whole array on every append, so the work grows with the square of the length. When you collect items one by one, a queue is the better tool, as the next lesson shows.
Copying an array copies the elements
Assigning one array to another copies every element. After snapshot = exp_beat;, the two arrays are separate, so changing snapshot[0] leaves exp_beat[0] as it was.
This differs from many software languages, where an assignment makes a second name for the same list. In SystemVerilog, a dynamic array on the left of an assignment is resized to match the one on the right.
Check every beat
Size the expected-data array from the request, and count the beats that actually arrive. At the last beat, the count must equal exp_beat.size(), and any beat that arrives after that is an error of its own.
Check the index before you use it as well. A beat number from the DUT can be wrong, and reading past the end returns a default value that may happen to match the data.
The last-beat flag gives a second check. On an AXI-style bus, rlast must be set on beat len - 1 and on no other beat. An early or missing rlast is an error even when every data value matches.
Choosing between fixed and dynamic
Use a fixed array when the count is part of the hardware and never changes during a test, such as the 4 byte lanes of a 32-bit bus. Its size documents the design, and the compiler can check constant indexes against it.
Use a dynamic array when each transaction brings its own count. Sizing a fixed array for the largest burst wastes memory: 1,000 outstanding reads with room for 256 beats each reserve 256,000 elements, even when most bursts carry 4 beats.
A dynamic array also keeps the size honest. exp_beat.size() always equals the burst length you allocated, so a loop over it covers exactly the beats that the request promised.
Try it
The fixed array silently dropped beats 4 and 5, so reading beat 5 returns x. The dynamic array was sized from the burst length and holds all 6 beats; growing it with new[8](exp_beat) kept them and added two x elements.
Changing the copy left the original alone. Now delete (exp_beat) from the new[8] line and predict the next display line before you run it.
fixed: $size=4, beat 5 reads xxxxxxxx new[6]: size=6, beat 0=xxxxxxxx new[8](exp_beat): beat 5=00000105, beat 7=xxxxxxxx after copy: exp_beat[0]=00000100 snapshot[0]=0000dead new[2] without copy: beat 0=xxxxxxxx after delete: size=0Check yourself
Remember
- A fixed array has a compile-time size; a dynamic array starts empty and is sized with
new[n]at run time. - Out-of-range writes are ignored and out-of-range reads return the default value, with no error.
new[n](old)keeps the old elements; plainnew[n]discards them.- Assigning one array to another copies every element; the two arrays are then separate.
- Size burst storage from the request, and count the beats that actually arrive.
Practice
- Resize and preserve Implement · Easy