How to Generate an Array of Unique Random Values in SystemVerilog
In short: To fill an array with values that never repeat, mark the array rand and add one line: constraint c { unique {arr}; }. The solver then gives every element a different value. On an old simulator that doesn’t know unique, use a nested foreach with !=. And if you don’t need the solver at all, fill the array yourself and call shuffle().
I keep running into the same small need in testbenches.
I want a handful of values, and no two can be the same.
Transaction IDs. Memory addresses that must not overlap. Register values. Channel numbers.
It sounds trivial. But if a duplicate sneaks in, the test can pass for the wrong reason, and you won’t notice until much later.
Here’s a number that keeps me careful. In the 2024 Wilson Research Group study, only 14% of chip projects got working first silicon (Siemens EDA, 2025). The study doesn’t say duplicate values caused that. It’s just a reminder that a case you never generate is a case you never check.
Think of it like train seats
Picture a train coach with 16 seats.
The booking system never sells seat 7 twice. Nobody walks the aisle checking tickets by hand. The rule “one seat, one passenger” lives inside the system.
The unique constraint is that rule for your array.
And if 20 people try to book 16 seats? Four of them get turned away. The solver does the same thing: ask for more unique values than exist, and randomize() fails.
Keep that picture in mind. It explains most of the mistakes further down.
A working example: unique AXI write IDs
Here’s a case from a normal AXI master testbench.
I want 8 write transactions in flight at the same time. AXI lets responses come back out of order when transactions use different IDs. Transactions with the same ID must complete in order (Arm AMBA AXI protocol specification, IHI 0022).
So if I want to really stress out-of-order responses, every outstanding write needs its own AWID.

My IDs are 4 bits wide, so there are 16 to choose from. And I’ll keep ID 15 back for debug traffic.
// Pick 8 different write IDs for 8 outstanding AXI writes.
class axi_wr_id_plan;
rand bit [3:0] awid[8]; // 4-bit IDs: 16 possible values
constraint c_no_repeat { unique {awid}; }
constraint c_keep_15_free {
foreach (awid[i]) awid[i] != 4'd15; // ID 15 is for debug traffic
}
endclass
module tb;
initial begin
axi_wr_id_plan plan = new();
repeat (3) begin
if (!plan.randomize())
$error("Could not pick 8 unique write IDs");
else
$display("AWIDs for this burst of writes: %p", plan.awid);
end
$finish;
end
endmodule
Expected output (not simulation-tested, so run it on your own simulator or EDA Playground to confirm): three lines, each with 8 different values from 0 to 14. The values change with the seed. No line ever contains 15, and no value repeats within a line.
That’s 8 IDs out of 15 allowed values, so the solver always has room. Change awid[8] to awid[16] and it has to fail, because only 15 values are allowed. That’s the train again: 16 passengers, 15 seats.
Three ways to get no-repeat values
Method 1: the unique constraint (use this first)
This is the one I reach for. It came in with IEEE 1800-2012 and is in section 18.5.5 of the standard (“Uniqueness constraints”).
You can mix whole arrays and single variables in one unique list. Everything in the list ends up different from everything else.
class dma_channels;
rand bit [2:0] ch_a, ch_b; // two single channels
rand bit [2:0] ch_pool[4]; // plus a pool of four
// All six values come out different: 6 of the 8 possible channels.
constraint c_all_diff { unique {ch_a, ch_b, ch_pool}; }
endclass
It reads like the requirement itself. A reviewer understands it in two seconds, and that matters more than people think.
Method 2: nested foreach (for tools that don’t support unique)
Some older simulators don’t know the unique keyword (see mistake 2 below). On those, you compare every pair yourself:
class axi_wr_id_plan_old;
rand bit [3:0] awid[8];
constraint c_no_repeat {
foreach (awid[i])
foreach (awid[j])
if (i < j) awid[i] != awid[j]; // check each pair once
}
endclass
It works, but the number of pairs grows fast: 8 elements make 28 pairs, and 64 elements make 2,016. If your tool supports unique, use it.
Method 3: fill and shuffle (no solver at all)
Sometimes you don’t need random values. You just need a fixed set in a random order. For example, “use IDs 0 to 7, but in a different order every run”.
Then skip the solver: fill the array, then shuffle it.
class axi_id_shuffle;
bit [3:0] awid[8];
function void build();
foreach (awid[i]) awid[i] = 4'(i); // 0, 1, 2 ... 7
awid.shuffle(); // same values, new order
endfunction
endclass
module tb;
initial begin
axi_id_shuffle s = new();
for (int run = 0; run < 3; run++) begin
s.build();
$display("run %0d: %p", run, s.awid);
end
$finish;
end
endmodule
Output (tested on Verilator 5.020, seed 7, 6 Nov 2026):
run 0: '{'h6, 'h7, 'h4, 'h3, 'h5, 'h2, 'h0, 'h1}
run 1: '{'h2, 'h3, 'h5, 'h0, 'h1, 'h6, 'h7, 'h4}
run 2: '{'h4, 'h2, 'h7, 'h5, 'h6, 'h3, 'h0, 'h1}
Every run holds 0 to 7 exactly once, in a new order. With another seed the order changes, but the set never does.
The catch: you always get the same set of values. If you need values picked from a bigger range, like 8 IDs out of 16, use Method 1.
Which one should you use?
Method 1: unique | Method 2: nested foreach | Method 3: fill + shuffle | |
|---|---|---|---|
| Values picked at random from a range | Yes | Yes | No, the set is fixed |
| Order is random | Yes | Yes | Yes |
| Needs the constraint solver | Yes | Yes | No |
| Works on older tools | Needs IEEE 1800-2012 support | Yes | Yes |
| Speed on big arrays | Good | Slows down (pairs grow fast) | Fastest |
| Lines of code | 1 | 4 | 2 |
My rule of thumb: start with Method 1. Use Method 3 when the set of values is fixed. Use Method 2 only when your tool forces you to.

Five mistakes engineers actually hit
These all come from real questions on the Verification Academy forums. I’ve retold them briefly and linked each one so you can read the full thread.
1. Asking for more values than exist
This is the train with too many passengers. A bit [3:0] has 16 possible values. Ask for 17 unique ones and the solver can’t do it, so randomize() returns 0.
Always check that return value. If you don’t, the array keeps its old values and the test carries on quietly with bad data.
2. A simulator that’s too old
In 2016 an engineer tried a basic unique example and got a compile error saying the keyword wasn’t expected there. The cause was a simulator release from 2013, which predated support for the 2012 feature. Newer versions compiled the same code fine (thread).
If you see that error on correct code, check your tool version before you debug the code. Method 2 is your fallback.
3. unique on an array of unpacked structs
In January 2026 someone wanted the addr field to be unique across an array of packet structs. Putting the struct array straight into unique doesn’t compile, because unique needs plain integral values (thread).
The fix is a small helper array. Make the helper unique, then tie each struct’s field to it:
typedef struct { rand bit [7:0] addr; rand bit [15:0] data; } pkt_t;
class pkt_list;
rand pkt_t pkt[10];
rand bit [7:0] addr_copy[10]; // helper
constraint c_unique_addr { unique {addr_copy}; }
constraint c_tie {
foreach (pkt[i]) pkt[i].addr == addr_copy[i]; // same value as the helper
}
endclass
4. A sum that quietly overflows
In 2023 an engineer asked for unique int values whose sum stayed between 0 and 100. The values came out huge, positive and negative, and they thought randomize() had broken. It hadn’t. int is signed and 32 bits wide, so the sum wrapped around and landed in range by accident (thread).
The fix: put a range on every element, not just on the sum, and make the sum constraint something the elements can actually reach.
5. Writing one rule per pair by hand
Back in 2014, someone had 16 separate 4-bit registers that all had to differ. They wrote a long !(... inside {...}) line for each one, and it didn’t work. Dave Rich of Siemens replied with a single unique line, then asked: “But why not use an array?” (thread).
He’s right. When values belong together, put them in an array, and the constraint stays one line.
Frequently asked questions
What’s the difference between unique and randc?
randc works on one variable across many randomize() calls. It cycles through every value before repeating one. unique works across many variables in a single call: they all differ from each other right now.
Does unique work on dynamic arrays and queues?
Yes. Set the size first, or constrain it in the same block, so the solver knows how many values to pick.
Can I keep certain values out?
Yes. Add a normal constraint next to it, like the foreach (awid[i]) awid[i] != 4'd15; line in the AXI example.
Is unique slow on big arrays?
It’s much better than writing out every pair. If you only need a fixed set in a random order, Method 3 skips the solver and is the fastest.
What to read next
- randc behavior from a rand variable, for no repeats across calls
- rand_mode and constraint_mode, to switch these rules on and off
- Arrays and queues in SystemVerilog, for picking the right array type
- AXI protocol verification guide, where unique IDs matter for out-of-order testing
References
- IEEE Std 1800-2017, SystemVerilog, section 18.5.5 “Uniqueness constraints” (the feature was added in IEEE 1800-2012)
- Arm, AMBA AXI protocol specification (IHI 0022), transaction ID ordering rules
- Siemens EDA and Wilson Research Group, 2024 IC/ASIC Functional Verification Trend Report (Feb 2025)
- Verification Academy forum threads linked in each mistake above (accessed 6 Nov 2026)
Code note: Method 3 was run on Verilator 5.020 on 6 Nov 2026. Methods 1 and 2 and the struct example follow IEEE 1800 but were not simulated here, because Verilator 5.020 doesn’t support unique or randomizing arrays. Run them on your simulator or EDA Playground to confirm.
Part of the learning path SystemVerilog for verification

One more way to generate unique values in array
class set_unique_val;
rand bit [3:0] data[10];
constraint uniq {
foreach(data[i])
foreach(data[j])
if(i!=j)
data[i] != data[j];
}
endclass : set_unique_val