Generate randc behavior from rand variable:

In short: A randc variable walks through every value in its range once before any value repeats. You can copy that no repeat behaviour with a plain rand variable by keeping a list of values already produced and adding a constraint that excludes them. In post_randomize() you push the new value onto the list; a unique constraint keeps the next solve out of that list. When the list is full you clear it in pre_randomize() to start a fresh cycle.

Sometimes you want cyclic, no repeat randomization but the variable is already rand, or you need finer control than randc gives. A small pattern using a history list and a unique constraint gives you randc style coverage from a rand variable.

A simple way to picture it

Think of a music player on shuffle with “no repeats” turned on. It plays each song in the album once in a mixed order, and only after every song has played does it allow repeats and start a new shuffle. To do this it keeps a list of songs already played and skips them until the list is full, then clears the list. The history list is our queue, skipping played songs is the unique constraint, and clearing the list starts the next cycle.

The pattern: history list plus a unique constraint

The idea has three parts. A queue stores values already produced. A unique constraint forbids the next value from matching anything in the queue. And post_randomize() adds each new value to the queue after a successful solve.

class cyclic_gen;
  rand bit [2:0] value;          // range 0..7
  bit [2:0] history[$];          // values seen this cycle

  // Next value must differ from every value already seen
  constraint c_no_repeat { !(value inside {history}); }

  function void post_randomize();
    history.push_back(value);    // remember what we produced
  endfunction
endclass

The constraint !(value inside {history}) keeps each solve out of the history, so within one cycle no value repeats, exactly like randc.

Starting a new cycle

The tricky part is knowing when a cycle ends. If you know the number of possible values, clear the history in pre_randomize() once it is full, so the next solve starts a clean cycle.

class cyclic_gen;
  rand bit [2:0] value;
  bit [2:0] history[$];
  localparam int LIMIT = 8;      // 2**3 possible values

  constraint c_no_repeat { !(value inside {history}); }

  function void pre_randomize();
    if (history.size() == LIMIT)
      history.delete();          // full cycle done, start over
  endfunction

  function void post_randomize();
    history.push_back(value);
  endfunction
endclass

Full example with a driver loop

Putting it together, this testbench randomizes 16 times. The first eight values cover 0 through 7 in a mixed order with no repeats, then the history clears and the next eight repeat the pattern with a fresh order.

class cyclic_gen;
  rand bit [2:0] value;
  bit [2:0] history[$];
  localparam int LIMIT = 8;

  constraint c_no_repeat { !(value inside {history}); }

  function void pre_randomize();
    if (history.size() == LIMIT) history.delete();
  endfunction
  function void post_randomize();
    history.push_back(value);
  endfunction
endclass

module tb;
  initial begin
    cyclic_gen g = new();
    for (int i = 0; i < 16; i++) begin
      if (!g.randomize()) $fatal(1, "randomize failed at i=%0d", i);
      $write("%0d ", g.value);
      if ((i+1) % 8 == 0) $display("  <- cycle complete");
    end
  end
endmodule

Expected output, in plain words

You get two groups of eight numbers. Within each group every value from 0 to 7 appears exactly once, in a mixed order, so no value repeats until the cycle completes. After the eighth value the history clears and a new cycle begins, so the second group again covers 0 to 7 in its own order.

3 0 5 1 7 2 4 6   <- cycle complete
5 2 0 7 1 3 6 4   <- cycle complete

Note: the exact order varies by seed; the point is that each cycle of eight has no repeats. This describes intended behaviour from an LRM read, not a captured run. Confirm on your own simulator or EDA Playground.

If you do not know the value count

When the number of possible values is not known ahead of time, you cannot clear on a fixed limit. Instead, watch the return value of randomize(). Once every value has been used, the unique constraint has no legal value left and the solve fails. Treat that failure as the end of a cycle: clear the history and randomize again.

if (!g.randomize()) begin
  g.history.delete();   // cycle exhausted, reset
  void'(g.randomize()); // start the next cycle
end

randc vs the rand plus history pattern

PointBuilt in randcrand + history list
No repeat per cycleYes, automaticYes, via unique constraint
Extra codeNonepre and post randomize plus a queue
Control over cycle endAutomaticYou decide, by limit or by failure
Works with wide rangesLimited by tool supportCosts memory for the history list
Best whenrandc fits your variableThe variable must stay rand or you need custom control

Common mistakes to avoid

  • Never clearing the history: once every value is used, randomize fails forever unless you reset the list.
  • Wrong limit value: set the limit to the true number of possible values, or a cycle ends early or never.
  • Growing the history without bound: for wide variables the list can use a lot of memory; prefer real randc when it fits.
  • Ignoring the randomize return: the failure at cycle end is your signal to reset when the count is unknown.

For the basics of the two randomization entry points, see randomize vs std::randomize. To manage which rules are active, read constraint override and enable and disable constraint. More is in our SystemVerilog tutorials.

Frequently asked questions

How do I get randc behavior from a rand variable?

Keep a queue of values already produced, add a unique constraint that excludes that queue, push each new value in post_randomize, and clear the queue to start a new cycle. This gives no repeat cycling like randc.

What does the unique or history constraint do?

The constraint !(value inside {history}) forbids the next solved value from matching any value already stored in the history queue, so no value repeats within a cycle.

When should I clear the history list?

If you know the number of possible values, clear it in pre_randomize once the list reaches that count. If you do not know the count, clear it when randomize fails, which signals the cycle is exhausted.

Why does randomize fail at the end of a cycle?

Once every possible value is in the history, the unique constraint has no legal value left, so the solver returns 0. Treat that as the end of a cycle and reset the history.

What is the difference between randc and this pattern?

randc gives no repeat cycling automatically with no extra code. The rand plus history pattern does the same by hand and lets you control when a cycle ends, but it costs memory for the history list.

When should I use this instead of randc?

Use it when the variable must stay rand, when randc is not well supported for your case, or when you need custom control over when a cycle restarts.

Similar Posts

One Comment

  1. Another way I tried.
    class rranc ;
    rand bit [15:0] val;
    int qval[$];

    constraint c_r{
    !(val inside {qval}) ;
    }

    function void post_randomize();
    if(qval.size >= 2**$bits(val)-1)
    qval = {};
    qval.push_back(val);
    endfunction
    endclass

    module top;
    initial begin
    rranc r = new ;
    repeat(32) begin
    r.randomize();
    end
    $display(“%p”,r);
    end
    endmodule

Comments are closed.