Constrain random variable result to enumerated type

I’ve got two random variables; the first one is an enumerated type which selects 1 of 3 “sets”.

Depending on the randomly chosen set, I need the second random variable “inst” to be confined to a specific list of numbers, specified as an enumerated type.

The following simple example can illustrate:
eda playground

A couple approaches I’ve tried were to simple create instances of each inst list, and then use implication constraints to assign the inst.

Also, I can do this in the post_randomize, but I would prefer using constraints.

In reply to bmorris:

here’s my attempt using implication constraints. seems to work. Just curious if anyone else had an alternate approach.

eda playground

Added the constraint

constraint ct_inst { set==SET1 -> inst inside {[0:2]}; 
	                 set==SET2 -> inst inside {[34:36]};
	                 set==SET3 -> inst inside {[114:116]};}

Also

if(!my_class.randomize()) $error;  // <-- Use this 
      // my_class.randomize();  // Instead of this

Results

run -all;
# KERNEL: '{set:SET2, inst:34}
# KERNEL: '{set:SET2, inst:34}
# KERNEL: '{set:SET1, inst:1}
# KERNEL: '{set:SET2, inst:36}
# KERNEL: '{set:SET1, inst:2}
# KERNEL: '{set:SET3, inst:115}
# KERNEL: '{set:SET3, inst:114}
# KERNEL: '{set:SET3, inst:115}
# KERNEL: '{set:SET1, inst:1}
# KERNEL: '{set:SET1, inst:2}

Ben Cohen http://SystemVerilog.us

typedef enum bit [3:0] { SET1, SET2, SET3 } e_sets ;

typedef enum bit [7:0] { FLEE=  0, JUMP=   1, FLY=     2 } e_set1_instructions ;
typedef enum bit [7:0] { GROW= 34, SLEEP= 35, AWAKEN= 36 } e_set2_instructions ;
typedef enum bit [7:0] { RUSH=114, STARE=115, WAIT=  116 } e_set3_instructions ;

class a_class;

  // command word
  rand e_sets      set; 
  rand bit [7:0]  inst;   

constraint order { solve set before inst;} 
  
  // I need a constraint to force inst into a list defined by either
  // e_set1_instructions, e_set2_instructions, or e_set3_instructions???
  
constraint ct_inst { set==SET1 -> inst inside {[0:2]}; 
	                 set==SET2 -> inst inside {[34:36]};
	                 set==SET3 -> inst inside {[114:116]};}

endclass

// ---------------------------------------------------------------------------
module top;
  
  a_class my_class=new();
  
  initial begin
    
    for (int i=0;i<10;i++) begin
    	if(!my_class.randomize()) $error;  // <-- Use this 
      // my_class.randomize();  // Instead of this 
      $display("%p", my_class);
    end
    
  end
  
endmodule

In reply to ben@SystemVerilog.us:

Is the solve set before inst really necessary? Maybe in some cases you actually want to constraint inst to a certain value. Having the order constraint will make it more verbose. You’ll need:


obj.randomize() with { set == SET1; inst == 1 };

to choose a value of one for inst, instead of


obj.randomize() with { inst == 1 };

if you wouldn’t have a solve order defined.

In reply to Tudor Timi:

ben,

   if(!my_class.randomize()) $error;  // <-- Use this 
      // my_class.randomize();  // Instead of this

Yup, was lazy ;)

constraint ct_inst { set==SET1 -> inst inside {[0:2]}; 
	                 set==SET2 -> inst inside {[34:36]};
	                 set==SET3 -> inst inside {[114:116]};}

The only problem being that in my real application the numbers are not simple series, and I don’t want repeat the numbers in the constraint, I only want them specified in one place, the enumerated type definition. (no magic numbers)
It would be nice to simply say “inst inside set1_inst_list”, but alas, it’s not an actual array.
But, yes I am using an implication operator, the only difference is I created random variables of the enumerated type, and then use those the constraints. (see my link below)


Tudor,

I don’t believe the order constraint is necessary. It seems to have no effect; not even on running…

obj.randomize() with { set == SET1; inst == 1 };

vs

obj.randomize() with { inst == 1 };

So I removed it.

my current solution is: EDA

For the life of me, I don’t understand why the implication operator → works for the case of

obj.randomize() with { inst == 1 };

It seems as though the instruction would be 1, but the set would be random. (the implication is not bidirectional)
However, the set always matches up correctly (SET1 in this case).

I would have expected it to only work with the bidirectional operator <->.

Thoughts?

In reply to bmorris:

The solve before constraint is one of the most misunderstood constructs in SystemVerilog. It is not really a constraint in that it has no effect on the available solution set. It only effects the distribution in the choice of values among the available solutions.

If the three sets of instructions have an equal number of non-overlapping values, then solve before will have absolutely no effect, and can be removed without changing any results.

Without any constraints, there are 3 possible values for set, and 256 possible values for inst. The constraints give us 9 possible solutions, still with 3 values for set, but only 9 possible values for inst (because each value for set only allows 3 values for inst). If you look at the total possible allowed combinations of set and inst, you will see that each of the values of set are in the solution space 3 times.

Solution space
set   inst
SET1  1
SET1  2
SET1  3
SET2  34
SET2  35
SET2  36
SET3  114
SET3  115
SET3  116

When you call randomize() without any solve before constraint, the solver will pick one of these solutions randomly with a uniform distribution. So the probability of of picking SET1 is 3/9 or 1/3rd. The probability of picking a value of any instruction is 1/9. If we change the example so that each set had 5 instructions, then the total number of solutions would go up to 15, but the probability of picking SET1 is still 5/15 or 1/3rd.

But now let’s change the example so that the number of instructions in each set is different. For example, SET1 has 3 instructions, SET2 has 7 instructions, and SET3 has 90 instructions. Now the total number of possible solutions is 100, and the probability of picking SET1 is 3/100 or 3%. The probability of picking a any particular instruction is 1/100. If we add the solve set before inst construct, that doesn’t change the fact that there are still only 100 possible solutions. Instead of picking a random solution from the collection of 100 possible, pick a value for set from the possible choices in the solution space first, eliminate the solutions that don’t have set with the value you picked, then proceed to pick a value for inst from the remaining solutions. By picking a value for set first, you now have a 1/3 probability of picking SET1, SET2, or SET3. But now the probability of picking one instruction from SET1 is (1/3)(1/3) or 1/9, and the probability of picking any instruction from SET3 is (1/3)(1/90) or 1/270.

Going back to the original example with

obj.randomize() with { inst == 1 };

This works without having to constrain set==SET1 because the 2 out of the 3 implication constraints could not be satisfied if set did not have the value SET1. (i.e. if set==SET2 then set==SET2 → inst inside {[34:36]}; cannot be satisfied if inst is constrained to 1. Also, thesolve set before inst has no effect because the total solution space is constrained to have only one value for set anyways.

1 Like

In reply to dave_59:

I like your example of the “solve before” constraint; very clear.

I was getting hung up on the fact that the implication operator is not bidirectional, while forgetting that my other 2 constraints were responsible for shrinking down my solution space such that there were no results possible for: E.g SET2 inst=1, or SET3 inst=1.

Illustrating the solution space helps a lot; sounds like the key is being able to look at your constraints to accurately determine the solution space. Then, it would make the job much easier of seeing how in-line or additional constraints would then shrink it further, or affect the distribution in the “solve before” case.

In reply to bmorris:

I’ve brought shame to my clan because of my wrong understanding of solve … before. I’m more of an e guy and I have the impression (though I’m not sure anymore) that there it use to be like this for gen … before constraints (i.e. they could cause contradictions, but I’ve tried it out now and the behavior in Specman is also as Dave describes for SystemVerilog).

The key statement from the LRM is this:

[…] a “solve…before…” constraint does not change the solution space and, therefore,
cannot cause the solver to fail.

I had the skewed interpretation that set would be solved before any of the constraints on instr are processed and that the solver would choose some value for set from SET1, SET2, SET3 and then try to solve instr, potentially leading to a contradiction. Silly me!

Hi forum colleges.
This thread helped me allot in understanding what exactly is ordering constraint.
I wrote a code that summarize all the above discussions.
If its appropriate I want to put it here so other readers can copy it and test it in their machines.

example 1 is bmorris original question
example 2 is dave example where for each enum value there may be different number of inst values
Here are the results of simulation

For set object, number of SET1=986, SET2=1008, SET3=1006 WITHOUT_ORDERING
For set object, number of SET1=988, SET2=1000, SET3=1012 WITH_ORDERING
For feel object, number of BAD=516, GOOD=832, NONE=1652 WITHOUT_ORDERING
For feel object, number of BAD=1023, GOOD=1014, NONE=963 WITH_ORDERING


package tb_env;
  import uvm_pkg::*;
  `include "uvm_macros.svh"
  
  typedef enum bit [3:0] { SET1,     SET2,      SET3 } e_sets ;
  typedef enum bit [7:0] { FLEE=0,   JUMP=1,    FLY=2 } e_set1_instructions ;
  typedef enum bit [7:0] { GROW=34,  SLEEP=35,  AWAKEN=36 } e_set2_instructions ;
  typedef enum bit [7:0] { RUSH=114, STARE=115, WAIT=116 } e_set3_instructions ;
  
  typedef enum bit [3:0] { BAD, GOOD, NONE } e_feel ;
  typedef enum bit [7:0] { SAD=0, GRIEF=1, UNHAPPY=2 } e_feel_bad ;
  typedef enum bit [7:0] { SMILE=34, JOY=35,  HAPPY=36,  EXITED=37, LAUGH=38} e_feel_good ;
  typedef enum bit [7:0] { APATHETIC=114, LETHARGIC=115, INDIFFERENT=116, POCOCURANT=117, SLUGGISH=118,
  INERT=119, IMPASSIVE=120, PHLEGMATIC=121, LACKADAISICAL=122, NEUTRAL=123} e_feel_none ;

  typedef enum bit {WITHOUT_ORDERING=0, WITH_ORDERING=1} e_order;

  class infra;
    static function void print_pair(input bit is_display, input string str1, str2);
      if (is_display) $display("{%s, %s}",str1, str2);
    endfunction
  endclass

  class sets extends uvm_object;
    
    rand e_sets      set; 
    rand bit [7:0]  inst;   
    e_set1_instructions set1;
    e_set2_instructions set2;
    e_set3_instructions set3;
    
    static int num_of_SET1;
    static int num_of_SET2;
    static int num_of_SET3;

    bit is_display=1'b0;

    constraint set_c 
    {
      (set == SET1) -> inst inside {[0:2]};
      (set == SET2) -> inst inside {[34:36]};
      (set == SET3) -> inst inside {[114:116]};
    }
    
    constraint order {solve set before inst;}
    
    `uvm_object_utils(sets)
    
    function new (string name = "sets");
      super.new(name);
    endfunction
    
    function void post_randomize();
      if (set==SET1) begin
        set1=e_set1_instructions'(inst);
        num_of_SET1++;
        infra::print_pair(is_display, set.name(), set1.name());
      end
      else if (set==SET2) begin
        set2=e_set2_instructions'(inst);
        num_of_SET2++;
        infra::print_pair(is_display, set.name(), set1.name());
      end
      else if (set==SET3) begin
        set3=e_set3_instructions'(inst);
        num_of_SET3++;
        infra::print_pair(is_display, set.name(), set1.name());
      end
    endfunction

    static function void clear_static();
      num_of_SET1=0; num_of_SET2=0; num_of_SET3=0;
    endfunction
    
  endclass
  
  class feels extends uvm_object;
    
    rand e_feel feel;
    rand bit [7:0] inst;
    e_feel_bad feel_bad;
    e_feel_good feel_good;
    e_feel_none feel_none;
    
    static int num_of_feel_bad;
    static int num_of_feel_good;
    static int num_of_feel_none;

    bit is_display=1'b0;
    
    constraint c_feel
    {
      (feel==BAD) -> inst inside {[0:2]};
      (feel==GOOD) -> inst inside {[34:38]};
      (feel==NONE) -> inst inside {[114:123]};
    }
    
    constraint order {solve feel before inst;}

    `uvm_object_utils(feels)
    
    function new (string name = "feels");
      super.new(name);
    endfunction
    
    function void post_randomize();
      if (feel==BAD) begin
        feel_bad=e_feel_bad'(inst);
        num_of_feel_bad++;
        infra::print_pair(is_display, feel.name(), feel_bad.name());
      end
      else if (feel==GOOD) begin
        feel_good=e_feel_good'(inst);
        num_of_feel_good++;
        infra::print_pair(is_display, feel.name(), feel_good.name());
      end
      else if (feel==NONE) begin
        feel_none=e_feel_none'(inst);
        num_of_feel_none++;
        infra::print_pair(is_display, feel.name(), feel_none.name());
      end
    endfunction
    
    static function void clear_static();
      num_of_feel_bad=0; num_of_feel_good=0; num_of_feel_none=0;
    endfunction
    
  endclass
  
  class var_order_test extends uvm_test;
    
    sets set;
    feels feel;
    
    `uvm_component_utils(var_order_test)
    
    function new (string name, uvm_component parent);
      super.new(name, parent);
    endfunction
    
    task run_phase (uvm_phase phase);
      super.run_phase (phase);
      
      // Run 2 iterations off 3000 randomization per iteration
      // in iteration 1 disable the ordering constraint and enable in 2
      // Static couters counts number of selection per enum value
      for (int j=0; j<2; j++) begin
        e_order e = e_order'(j);
        for (int i=0; i<3*1000; i++) begin
          set=sets::type_id::create("set");
          set.is_display=1'b0;
          set.order.constraint_mode(j);
          if (!set.randomize()) `uvm_error(get_name(), "Failed to randomize sets object")
        end
        $display ("For set object, number of SET1=%0d, SET2=%0d, SET3=%0d %s",
        set.num_of_SET1, set.num_of_SET2, set.num_of_SET3, e.name());
        sets::clear_static();
      end
      
      for (int j=0; j<2; j++) begin
        e_order e = e_order'(j);
        for (int i=0; i<3*1000; i++) begin
          feel=feels::type_id::create("feel");
          feel.is_display=1'b0;
          feel.order.constraint_mode(j);
          if (!feel.randomize()) `uvm_error(get_name(), "Failed to randomize feel object")
        end
        $display ("For feel object, number of BAD=%0d, GOOD=%0d, NONE=%0d %s",
        feel.num_of_feel_bad, feel.num_of_feel_good, feel.num_of_feel_none, e.name());
        feels::clear_static();
      end
    endtask
  endclass
endpackage : tb_env

module top;
  import uvm_pkg::*;
  `include "uvm_macros.svh"
  import tb_env::*;
  initial begin
    run_test("var_order_test");
  end
endmodule