Constrained Random Using Code Patterns

Having functions that do randomization is not constrained random. Instead, OSVVM creates a constrained random test environment by using code patterns in conjunction with randomization. This post shows a number of code patterns.

Weighted Sequences

The following example uses DistInt to generate the first test sequence 70% of the time, the second 20%, and the third 10%.

variable RV : RandomPType ;
. . . 
StimGen : while TestActive loop     -- Repeat until done
  case RV.DistInt( (7, 2, 1) ) is   
    when 0 =>  -- Normal Handling   -- 70%
       . . . 
    when 1 =>  -- Error Case 1      -- 20%
       . . . 
    when 2 =>  -- Error Case 2      -- 10%
       . . . 
    when others =>  
      report "DistInt" severity failure ;  -- Signal bug in DistInt
  end case ; 
end loop ;

Randomizing a Sequence Order

The following code segment generates the transactions for writing to DMA_WORD_COUNT, DMA_ADDR_HI, and DMA_ADDR_LO in a random order that is different every time this code segment is run. The sequence finishes with a write to DMA_CTRL. When DistInt is called with a weight of 0, the corresponding value does not get generated. Hence by initializing all of the weights to 1 and then setting it to 0 when it is selected, each case target only occurs once. The “for loop” loops three times to allow each transaction to be selected.

variable RV : RandomPType ;
. . . 
Wt0 := 1;  Wt1 := 1;  Wt2 := 1;  -- Initial Weights

for i in 1 to 3 loop             -- Loop 1x per transaction

  case RV.DistInt( (Wt0, Wt1, Wt2) ) is  -- Select transaction

    when 0 =>                    -- Transaction 0
      CpuWrite(CpuRec, DMA_WORD_COUNT, DmaWcIn);  
      Wt0 := 0 ;                 -- remove from randomization

    when 1 =>                    -- Transaction 1 
      CpuWrite(CpuRec, DMA_ADDR_HI, DmaAddrHiIn); 
      Wt1 := 0 ;                 -- remove from randomization

    when 2 =>                    -- Transaction 2 
      CpuWrite(CpuRec, DMA_ADDR_LO, DmaAddrLoIn); 
      Wt2 := 0 ;                 -- remove from randomization

    when others =>   report "DistInt" severity failure ;

  end case ; 
end loop ;

CpuWrite(CpuRec, DMA_CTRL, START_DMA or DmaCycle);

Non-repeating Values

The following code segment uses an exclude list to keep from repeating the last value.  When passing an integer value to an integer_vector parameter, an aggregate using named association “(0=> LastDataInt)” is used to denote a single element array. During the first execution of this process, LastDataInt has the value integer’left (a very small number), which is outside the range 0 to 255, and as a result, has no impact on the randomization.

RandomGenProc : process
  variable RV : RandomPType ; 
  variable DataInt, LastDataInt : integer ;
begin
  . . .  
  DataInt := RV.RandInt(0, 255, (0 => LastDataInt)) ; 

  LastDataInt := DataInt;
  . . .

The following code segment uses an exclude list to keep from repeating the four previous values.

RandProc : process
  variable RV : RandomPtype ; 
  variable DataInt : integer ; 
  variable Prev4DataInt : integer_vector(3 downto 0) := (others => integer'low) ;
begin
  . . .  
  DataInt := RV.RandInt(0, 100, Prev4DataInt) ; 

  Prev4DataInt := Prev4DataInt(2 downto 0) & DataInt ; 
  . . .

Randomization and Heuristics

Some test cases use heuristics to generate an appropriate sequence of actions. The following test generates random traffic to the FIFO by using heuristics (guesses) at length of bursts of data and delays between bursts of data.

variable RV : RandomPType ; 
. . .  
TxStimGen : while TestActive loop  
  -- Burst between 1 and 10 values
  BurstLen := RV.RandInt(Min => 1, Max => 10);
  for i in 1 to BurstLen loop 
    DataSent := DataSent + 1 ; 
    WriteToFifo(DataSent) ; 
  end loop ;
  --  Delay between bursts: (BurstLen <=3: 1-6, >3: 3-10) 
  if BurstLen <= 3 then 
    BurstDelay := RV.RandInt(1, 6) ;  -- small burst, small delay
  else
    BurstDelay := RV.RandInt(3, 10) ; -- bigger burst, bugger delay
  end if ; 
  wait for BurstDelay * tperiod_Clk - tpd ;
  wait until Clk = '1' ;
end loop TxStimGen ;

Summary

In OSVVM, code patterns plus calls to randomization functions are used to create a constrained random test.  While OSVVM does not provide a solver, using code does an adequate job for constrained random.

In fact, most constrained random tests are problematically slow.  This occurs because randomization is only uniform over large quantities of randomizations.  Verification on the other hand typically only requires 1 or 2 of each unique test sequence.   As a result, using randomization to generate N unique test cases requires O(N*Log N) randomizations.

To avoid generating “log N” extra test cases, OSVVM uses Intelligent Coverage™ randomization as its primary method of randomization. Intelligent Coverage™ is an Intelligent Testbench methodology that can does a random walk across the functional coverage model. As a result, closure of the functional coverage model using Intelligent Coverage is significantly faster than constrained random – even if using a constraint solver.  For more see blog post, “Intelligent Coverage basics.”