What are the common and good usage of pre_randomize() and post_randomize() in systemverilog? - system-verilog

How can I change/add constraint or constraint_mode in pre_randomize()?
I know I can overwrite results in post_randomize and I can call rand_mode on and off in pre_randomize, but I am looking for some additional functionality especially related to constraints.

pre_randomize is generally used to set some pre-conditions before object randomization. Here one can print the results of previous randomization, set some variables on which the constraints are dependent etc.
As you mentioned, pre_randomize can be used to set rand_mode(0) for any variable. It can be used to manipulate constraints as well.
post_randomize is used to manipulate some variables like ECC check, print randomization result, manipulate some non-random fields based on existing randomization etc.
One another usage of post_randomize is to generate 'x' or 'z' in randomization process. The randomization by default generates 0 and 1 known values only. But one can use the existing randomized variable to generate x/z values also.
Here is a dummy example of what we can do in pre_randomize and post_randomize functions. Here, depending on non_rand_var, we can enable/disable the constraint mode and set the rand mode of any variable. In the post_randomize function, one can overwrite 'my_x' variable byt 'x' or 'z'.
class A;
int non_rand_var;
rand int rand_var;
rand int rand_var2;
rand logic my_x;
constraint c1{non_rand_var==1 -> rand_var=='h5;}
function new(int non_rand_var);
this.non_rand_var = non_rand_var; // set non random variable
endfunction
function void pre_randomize();
if(non_rand_var==5) begin // set randomization mode of rand_var2
rand_var2.rand_mode(0);
c1.constraint_mode(0); // disable constraint
end
$display("In pre randomize, non_rand_var=0x%0x rand_var=0x%0x",non_rand_var, rand_var);
endfunction
function void post_randomize();
// my_x = $urandom_range(0,1) ? 0 : 'x;
my_x = (non_rand_var==1) ? 0 : 'x; // Manipulate my_x to generate 'x' values
$display("In post randomize, rand_var=0x%0x",rand_var);
endfunction
endclass
module top();
A a=new(1);
initial begin
a.randomize();
$display("Initial block:\na.my_x = 0x%0x\na.rand_var=0x%0x\na.non_rand_var=0x%0x\na.rand_var2=0x%0x",a.my_x,a.rand_var,a.non_rand_var,a.rand_var2);
end
endmodule

pre_randomize & post_randomize function can have several uses based on the application.
Here is the list of few usage of those functions.
Both functions can be overridden, and hence it can be possible to modify the randomization behavior with extended class
Turn on/off few random variables
Turn on/off few constraints
Assignments to other nonrandom variable, on which the randomization is dependent
Changing the weight of random variables, based on certain conditions
Typical usage of pre_randomization function is to generate an array of unique values.
class helper;
randc bit [7:0] a;
endclass
class original;
bit [7:0] unique[64];
function void pre_randomize();
helper h = new();
foreach (unique[i])
begin
void'(h.randomize());
unique[i] = h.a;
end
endfunction
endclass

Related

How to make randomize redo until get the number I want?

A basic sequence class for randomize reset below:
class random_reset;
rand int rst_period;
constraint rst_range { rst_period inside {[1:100]}; }
task random_system_reset (
ref reg rst,
ref reg clk);
begin
rst = 1;
repeat (rst_period) #(posedge clk);
rst = 0;
end
endtask
endclass
However, I test need at least 6 clocks. Got anyway to make sure when I call this class, will get the random value bigger than 6?
Assuming you want to keep your original constraint as-is, but in some circumstances, you need to enforce the period to be 6 or more, you can use randomize() with:
random_reset rr = new();
initial rr.randomize() with { rst_period >= 6; };
For this rr object, rst_period will be between 6 and 100.
Refer to IEEE Std 1800-2017, section 18.7 In-line constraints—randomize() with.

Way to have a function like urandom_range(); which will return unique values?

I want to have a urandom_range(); which will not repeat a value once its picked in a simulation ? If it has exhausted its supply of 'available' numbers, then perhaps it can repeat .
Is there any keyword in systemverilog which will help quickly to get around this ?
Not a SV expert here so an example would really help! Thanks
randc does exactly this. (cyclic randomization)
class A;
randc bit[7:0] m;
endclass
Each time you call randomize() on the same object, it will not repeat value for m until all possible values have been given.
Simulators have limits on how large the cyclic value can be, but the standard requires a minimum of 8-bits. If you have a larger value, then you can use the inside operator.
class A;
rand bit[23:0] r;
bit [23:0] list[$];
constraint c { !(r inside {list}); }
function void post_randomize();
list.push_back(r);
endfunction
endclass
If you really expect to cycle through the list, it might be simpler to build the list first, and then shuffle through the list.
bit [7:0] list[20];
for(int i=0;i<20;i++) list[i] = i+10; // range 10-29
list.shuffle();
// cycle through list[0] ... list[29]
list.shuffle();
// cycle through list[0] ... list[29]
You can declare a variable with randc identifier. This is called 'cyclical random' and will ensure exactly what you are requiring.
Note: This requires a license that supports randomization and random variables. Most commercial simulators do provide this but at a higher cost. If you are constrained by this and need to only use the system calls - $urandom or $urandom_range, I would implement something like a queue that tracks all the values returned.
function automatic void find_unique_num();
int c;
int vals[$];
bit found;
do begin
c = $urandom_range(10, 1);
foreach(vals[i])
if (c == vals[i]) found = 1;
end
while (!found);
vals.push_back(c);
return c
endfunction

how to generate array of interfaces having different connections?

I have declared following interface:
interface data_x #(parameter g_DataWidth = 8)
(input ckrs_t ClkRs_ix);
logic [g_DataWidth-1:0] data;
bit enable;
ckrs_t ClkRs;
always_comb begin
ClkRs = ClkRs_ix;
end
endinterface
The interface has data bus and the data enable, and it is as well associated with the clock and reset signal, which is a typedef ckrs_t.
I have a module, which accepts as an argument array of those interfaces:
module fourmclinks
(...
data_x packet_ox[NUMBER_OF_GBT_LINKS-1:0],
data_x packet_ix[NUMBER_OF_GBT_LINKS-1:0],
...
);
The problem I have is, that I need to declare in top-level entity an array of those data_x interfaces, but each time use different ClkRs_ix input clock. (It is used in the gbts, where each receiver has its own clock and reset signal).
I tried many things, including this one:
ckrs_t txclock_x;
assign txclock_x.clk = GbtTxFrameClk40MHz_k;
assign txclock_x.reset = GbtReset_r;
data_x #(.g_DataWidth(g_FrameSize)) packet_ox[NUMBER_OF_GBT_LINKS-1:0](.ClkRs_ix(txclock_x));
data_x #(.g_DataWidth(g_FrameSize)) packet_ix[NUMBER_OF_GBT_LINKS-1:0]();
genvar linkiface;
generate
for(linkiface=1; linkiface < NUMBER_OF_GBT_LINKS+1; linkiface++) begin : linkgenerator
assign packet_ix[linkiface-1].ClkRs_ix.clk =
GbtRxFrameClk40Mhz_kb4[linkiface];
assign packet_ix[linkiface-1].ClkRs_ix.reset = GbtReset_r;
assign packet_ix[linkiface-1].enable = 0;
assign packet_ix[linkiface-1].data = RxDataAppSfpGbtUserData_4b80[linkiface];
end
endgenerate
Hence making empty/virtual/unassigned/... interface array declaration, and then in generate loop assign correct signals to it. This simulates, but quartus does not compile it claiming
value cannot be assigned to input "ClkRs_ix".
How to correctly generate array of interfaces, each having different input connection? Please help
I'm bit smarter now, so here is the solution to the problem. But first issues:
it is not possible just to remove 'input' direction from the port declaration in the data_x interface declaration above. If this is done, one has to then manually assign clock and reset lines for every instance of the data_x object. This is indeed possible, but one loses all the beauty of having the clock and reset automatically assigned during the instantiation of the interface
it is not possible either in this particular case to make a virtual interface, and connect the signals in the for loop. Root cause of this is the presence of always_comb, which takes in the input reset/clock and assigns it to the internal signals. So this assignment, together with manual assignment of reset and clock in the top-level entity results in driving those signals from two sources, which Quartus will not digest
So the only possible way, which I found is following:
Declare the data_x interface to generate the always_comb on demand:
interface data_x #(
parameter g_hasClock = 1,
parameter g_DataWidth = 8)
(
input ckrs_t ClkRs_ix
);
logic [g_DataWidth-1:0] data;
bit enable;
ckrs_t ClkRs;
generate
if(g_hasClock) begin
always_comb begin
ClkRs = ClkRs_ix;
end
end
endgenerate
endinterface // data_x
Instantiate the interface with unbound ClkRs_ix. Note the usage of g_hasClock, which instantiates the data_x interface without always_comb block, hence Quartus stops complaining about multiple drivers:
data_x #(.g_DataWidth(g_FrameSize),
.g_hasClock(0)) packet_ix[NUMBER_OF_GBT_LINKS-1:0]();
And then generate interface with different clocks:
genvar linkiface;
generate
for(linkiface=1; linkiface < NUMBER_OF_GBT_LINKS+1; linkiface++)
begin : linkgenerator
assign packet_ix[linkiface-1].ClkRs.clk = GbtRxFrameClk40Mhz_kb4[linkiface];
assign packet_ix[linkiface-1].ClkRs.reset = GbtReset_r;
assign packet_ix[linkiface-1].enable = 0;
assign packet_ix[linkiface-1].data = RxDataAppSfpGbtUserData_4b80[linkiface];
end
endgenerate
This works. It is not so nice because we have to do it manually. Just for sake of completeness: if the clocks for all interfaces is the same, all that code above boils down to this snippet:
ckrs_t txclock_x, rxclock_x;
assign txclock_x.clk = GbtTxFrameClk40MHz_k;
assign txclock_x.reset = GbtReset_r;
data_x #(.g_DataWidth(g_FrameSize)) packet_ox[NUMBER_OF_GBT_LINKS-1:0](.ClkRs_ix(txclock_x));
I'm sure this is not the best solution ever, but it is compilable and gives the result needed.

How to generate ascending values during randomisation

Please help to resolve one randomization-constraint related issue that I am facing.
So in my seqItem, I have a write_addr random variable. This variable controls the location in memory where the data should be written.
I want to implement different writing address changing modes like random-address, given range address, ascending and descending type.
I have params_pkg, where user defines the address change type and my TB generates write_addr values correspondingly.
I was thinking to implement this using constraints, like by enableing/disabling the constrains get the required behavioral:
class seqItem extends uvm_sequence_item;
`uvm_object_param_utils(seqItem)
randc logic [541-1:515] wfifo_addr;
if (params_pkg::writeAddressType == "WriteGivenRangeAddress") begin
constraint wArrdGivnRangCnstr {
this.wfifo_addr inside {[params_pkg::addrLowValue:params_pkg::addrHighValue]};
}
end
function new (string name="seqItem");
super.new(name);
this.wArrdGivnRangCnstr.constraint_mode(0);
endfunction
endclass
However there is no way to generate ascending or descending address values using constraints. Because to have ascending address, the seqitem code needs to know the write_addr variable value from the previous randomization, which I could not implement.
My question is: whether it is possible to have ascending write_addr values using constraints?
And second, the example code that I posted is not working, simulator gives error saying that generate constraints are not allowed. Most probably this is something not supported in System Verilog. Am I right?
Second part first: I suspect "Generate constraints" are constraints contained within a generate block. Generate blocks are only allowed within modules, programs, and checkers not classes, hence "Generate constraints" are illegal (although the term is oddly specific. I'd expect an error saying "Generates are not allowed in classes"). You can rewrite your constraints by moving the conditional inside the constraint block:
constraint wArrdGivnRangCnstr {
if (params_pkg::writeAddressType == "WriteGivenRangeAddress") {
this.wfifo_addr inside {[params_pkg::addrLowValue:params_pkg::addrHighValue]
};
}
BTW: you might want to consider an enum for the writeAddressType variable. That way typos are caught at compile time.
Another thing...
You have your random variable defined as randc.
Variables declared with the randc keyword are random-cyclic variables that cycle through all the values in a random permutation of their declared range.
If you limit the range on a randc variable, it can't "cycle through all the values...of [it's] declared range". It's not clear from the SystemVerilog LRM what will happen here, but I suspect that once all the values between low and high are exhausted randomisation will fail.
Also, the variable is 26-bits in size. That's 67,108,864 different values you're asking the simulator to keep track of to see if they've been used before. It will need 8MB of flags just for this one variable.
I expect what you really want here is to define the variable as rand and not randc.
On to your main question...
You are right, you need some kind of storage of the last value in order to get incrementing values, and because this is a sequence item I suspect that you're creating a new instance each time, hence we can't store the last value in an instance variable as all instance variables are destroyed.
So there's two options:
Store the last value in a static instance variable.
class seqItem extends uvm_sequence_item;
`uvm_object_param_utils(seqItem)
rand logic [541-1:515] write_addr;
static logic [541-1:515] last_write_addr = 0;
constraint wAddrIncr {
write_addr > last_write_addr;
}
function new (string name="seqItem");
super.new(name);
endfunction
function post_randomize();
last_write_add = write_addr;
endfunction
endclass
Add a constraint in the sequence when randomising the sequence item
class someSequence extends uvm_sequence;
...stuff omitted...
task body();
seqItem item;
seqItem last_item;
last_item = null;
repeat (4728346) begin
item = new(); // or create to use factory
if (last_item) begin
item.randomize() with {
write_addr > last_item.write_addr
};
end else begin
item.randomize();
end
last_item = item;
// Send to driver or whatever
end
endtask
endclass
Number 2 is better in my opinion, because it doesn't bake the increasing address behaviour into the sequence item. Incrementing addresses is really a property of the sequence of items, not of any single item. I can now write some sequences that have increasing addresses, decreasing addresses, or any other pattern.
One solution to your problem of generating ascending (or descending) addresses is to keep a note of the last value generated and to use this as the lower bound in the constraint:
class seqItem;
randc logic [0:15] wfifo_addr;
static logic [0:15] last_wfifo_addr = '0;
constraint wArrdGivnRangCnstr {
this.wfifo_addr inside {[last_wfifo_addr:params_pkg::addrHighValue]};
}
function void post_randomize;
last_wfifo_addr = wfifo_addr;
if (last_wfifo_addr >= params_pkg::addrHighValue)
last_wfifo_addr= params_pkg::addrLowValue;
endfunction
endclass
http://www.edaplayground.com/x/3QxX
The post_randomize function is a built-in method that can be overrided. It is called implicitly after the (built-in) randomize method. There is also a built-in pre_randomize, which of course you can override, too.
One solution to your problem of changing randomisation modes, is to turn constraints on and off:
You can turn a constraint off using the implicit constraint_mode method:
s.wArrdGivnRangCnstr.constraint_mode(0);
and then turn it on again:
s.wArrdGivnRangCnstr.constraint_mode(1);
(where s is a reference to your seqItem class). You can't put constraints inside an if statement as your error message demonstrates.

Constraining an entire object in SystemVerilog

I'm trying to constrain an entire object (not just the fields of an object) based on some other object. Here is a stripped down version of my production code:
I have the following class:
class some_class;
bit[7:0] some_field;
bit[3:0] some_other_field;
// this function would do some complex procedural
// operations on the fields of the object
function void do_some_op();
bit[3:0] tmp = some_field[3:0];
some_field[3:0] = some_other_field;
some_other_field = some_field[7:4];
some_field[7:4] = tmp;
endfunction
function some_class some_function(bit some_param);
some_function = new this;
$display("foo"); // this print here to see that method is executed
if (some_param)
some_function.do_some_op();
endfunction
function void print();
$display("some_field = %x", some_field);
$display("some_other_field = %x", some_other_field);
endfunction
endclass // some_class
This class contains some integral fields. It also has a method that does some complex procedural on the fields of that class. In the example I've simplified it. I also have another class that returns a new object on which the operation has been performed.
I have another class that operates with some_class instances. As per Dave's input I have made it create the objects first (as randomize() does not create objects).
class some_shuffler;
rand bit params[];
rand some_class objects[];
constraint size_c {
params.size() == objects.size() - 1;
params.size() <= 10;
};
constraint shuffle_c {
// not allowed by standard
// foreach (params[i])
// objects[i+1].some_field == objects[i].some_function(params[i]);
foreach (params[i])
objects[i+1].some_field ==
objects[i].some_function(params[i]).some_field &&
objects[i+1].some_other_field ==
objects[i].some_function(params[i]).some_other_field;
};
function new();
objects = new[10]; // create more objects than needed
foreach (objects[i])
objects[i] = new();
// initialize first object
objects[0].some_field = 8'hA5;
endfunction // new
function void post_randomize();
foreach (objects[i]) begin
$display("objects[%0d]:", i);
objects[i].print();
$display("");
end
endfunction
endclass
This class has two arrays, one of operations performed and one of the intermediate states. There is an initial object. On this one, some_function is performed and it results in the next object.
This is how I wanted to test it:
module top;
import some_pkg::*;
initial begin
static some_shuffler shuffler = new();
bit rand_ok;
rand_ok = shuffler.randomize() with {
params.size() == 1;
};
assert (rand_ok);
end
endmodule
When trying to constrain the objects directly I immediately get a constraint violation. The simulator seems to try to make the 2 handles equal. This is anyway forbidden by the standard and I'm not doing it anymore (though a compile failure would have been nice). I've unraveled the constraints as suggested by Dave and Greg (I think doing some_function().some_field is non-standard, but it compiles in Questa).
Even now, the foo print does not appear on the command line (some_function() is not getting executed). What I see is that objects[1] contains the initial value (all 0s for both fields).
I can't just generate the list of params and then procedurally randomize the objects for each iteration, because I want to be able to constrain the last object to have a certain value - basically giving the constraint solver the start and the end points and let it figure out the way to get there.
Object vs. object constraints are not allowed in SystemVerilog because they are not integral types. See IEEE Std 1800-2012 § 18.3:
Constraints can be any SystemVerilog expression with variables and constants of integral type (e.g., bit, reg, logic, integer, enum, packed struct).
You can constrain the integral components of class object if the component is a rand (ex obj[1].value == obj[0].value+1;).
Functions are allowed in constraints, but there limitation. See IEEE Std 1800-2012 § 18.5.12 Functions in constraints for full details. Limitations include:
Functions cannot contain output or ref arguments
Functions should be automatic and leave no side effects
The functions arguments have an implicit priority (ex x<=F(y) infers solve y before x)
Circular dependencies will result in an error
Update:
Looks like the only thing truly being randomized is params. The values of some_field and some_other_fieldare calculations. So it makes more sense to move the loop for shuffling into thepost_randomize` function.
constraint size_c {
params.size() == objects.size() - 1;
params.size() <= 10;
};
function void postrand_shuffle();
foreach (params[i])
objects[i+1] = objects[i].some_function(params[i]);
endfunction
function void post_randomize();
postrand_shuffle();
// ... your other post_rand code...
endfunction
SystemVerilog's random constraint solver will work when there is at least one solution. However when the solution space is small and difficult to determine or a long chain, simulator performance drops. For these scenarios it is better move the one-to-one sequential calculations into post_randomize.
A couple of problems with your example code.
Objects must be constructed first before calling randomize(). If
you know the exact size before calling randomize (like in your
example), just new[n] the dynamic arrays constructing each object
element first, and remove the size constraints. If the size will be
random, you need an upper limit constraint on the size. construct the max
number of objects before calling randomize(), and after randomizing the array, the unused objects will be eliminated.
Constraint expressions must be integral. You can do objects[i+1].some_field == objects[i].some_field but the solver cannot manipulate class handles.
The return values of functions are treated as state variables. Move these to post_randomize