SystemVerilog: Randomization点点滴滴

本文详细探讨了SystemVerilog中的随机化机制,包括RANDOMIZE()与STD::RANDOMIZE()的使用区别,约束的添加,以及随机模式关闭后约束的有效性。此外,还讨论了pre_randomize()和post_randomize()在类继承中的调用顺序,以及它们的非虚方法特性。同时,介绍了随机化层次化和rand与randc的区别,帮助读者深入理解SystemVerilog的随机化设计和验证。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

0. 前言

1. RANDOMIZE() VS STD::RANDOMIZE()

2. ADDING CONSTRAINTS WITH STD::RANDOMIZE() WITH

3. 随机模式关闭后约束还有效吗?

4. pre/post_randomize()在子类和父类中的调用顺序是什么?

5. pre/post_randomize()是虚方法吗?

6. 随机化的层次化

7. 约束的几种形式

8. Rand 和randc的区别


0. 前言

        零零碎碎地记载一些关于SystemVerilog随机化的问题,结合代码实验进行解析。以下的代码的运行均利用windows版的QuestaSim。关于基本的仿真命令使用方法可以参考:数字电路设计仿真方法入门

        本笔记随时动态更新。 

1. RANDOMIZE() VS STD::RANDOMIZE()

        RANDOMIZE()是SV的类内建的随机化函数,STD::RANDOMIZE()是独立的随机化函数。

        每一个类都内建(built-in)虚方法 randomize(), 其声明如下(注意,如下面所述,回调函数pre/post_randomize()不是虚方法!): 

  virtual function int randomize();         

        randomize()用于对类中定义为rand或者randc类型的成员变量进行随机化处理。但是randomize()不会被自动调用。类中的成员定义为rand或者randc类型后,需要显式调用来产生所有随机变量的随机采样值(遵循约束条件)。关于class::randomize(),有以下几点需要注意:

  • If randomize() fails, then post_randomize() is not called. 关于post_randomize(),请参考后面章节。
  • randomize() method is built-in and cannot be overriden. randomize()不能被override!开发者不能自己去重新定义randomize()
  • If randomization fails, then the variables retain their original values and are not modified。如过随机化失败(比如说,约束条件无法满足),
class trans;
  rand  bit [3:0] data1;
  randc bit [3:0] data2;
  bit [7:0] data3;
  
  constraint c_data1 { data1 >= 8;}
  constraint c_data2 { data2 <  8;}
  
  function void pre_randomize();
    $display("trans: called before randomization!");
  endfunction  

  function void post_randomize();
    $display("trans: called after randomization!");
  endfunction  
 
endclass

module test1;
  initial begin
    int  var1;
    trans tr;
    tr = new();
    
    if(tr.randomize())
      $display("Randomization successful ! data1 = %0d \t data2 = %0d \t data3 = %0d",tr.data1,tr.data2, tr.data3);
    else
      $display("Randomization failed ! ");        
      
    void'(std::randomize(var1));
    $display("var1 = %0d",var1);
    
    void'(std::randomize(tr.data1));
    void'(std::randomize(tr.data3));
    $display("after std::randomize(): tr.data1 = %0d, tr.data1 = %0d",tr.data1,tr.data3);
  end
endmodule

        std::randomize()可以用于对当前作用域中任何变量进行随机化处理,即便该变量并没有被声明为随机化类型。

        std::randomize()也可以用于对类的成员进行随机化处理!不管它是否被定义为rand类型。虽然这个并不在SV标准要求的范围以内。

        运行以上代码会报告以下编译警告:

# ** Warning: pre_post_randomize.sv(77): (vopt-2961) Argument #1 for std::randomize() function is non-LRM compliant.
# ** Warning: pre_post_randomize.sv(78): (vopt-2961) Argument #1 for std::randomize() function is non-LRM compliant.

        但是它的确是能够工作的,运行结果如下所示:

 VSIM 1> run -all
# trans: called before randomization!
# trans: called after randomization!
# Randomization successful ! data1 = 14          data2 = 3       data3 = 0
# var1 = 393546790
# after std::randomize(): tr.data1 = 6, tr.data1 = 241

2. ADDING CONSTRAINTS WITH STD::RANDOMIZE() WITH

        不仅是类的内建randomize()可以带约束条件运行,std::randomize()也同样可以。当带约束调用std::randomize()时,randomize()的输入参数变量为待随机化变量,其它的变量则当作状态变量使用。如以下例码所示:

module test5;
  int x, y, z, status;
  int d_length;
  
  initial begin
    d_length = $urandom_range(1,1000);
    
    status = std::randomize( x, y, z ) with { x < y ; x + y < d_length ; };
    $display("\tx = %0d \t y = %0d \t z = %0d \t d_length = %0d", x,y,z,d_length);
    
    status = std::randomize( x, y ) with { y - x > d_length ; };
    $display("\tx = %0d \t y = %0d \t z = %0d \t d_length = %0d", x,y,z,d_length);
  end
endmodule

VSIM 1> run -all
#       x = -1763570032          y = 31477632    z = 877272384   d_length = 913
#       x = -1050698721          y = -438691868          z = 877272384   d_length = 913

3. 随机模式关闭后约束还有效吗?

        设计一个简单的代码实验如下所示:

//class
class trans;
  rand  bit [3:0] data1;
  randc bit [3:0] data2;
  
  constraint c_data1 { data1 >= 8;}
  constraint c_data2 { data2 <  8;}
endclass
 
module test3;
  initial begin
    trans tr;
    tr = new();
    tr.rand_mode(0);

    tr.randomize();
    $display("\tdata1 = %0d \t data2 = %0d",tr.data1,tr.data2);
    
    for(int i=0; i<8; i++) begin      
      tr.data1 = i;
      tr.data2 = i + 8;
      $display("\tdata1 = %0d \t data2 = %0d",tr.data1,tr.data2);      
    end
  end
endmodule

        编译运行以上test3,可以得到结果如下:

#       data1 = 0        data2 = 0
#       data1 = 0        data2 = 8
#       data1 = 1        data2 = 9
#       data1 = 2        data2 = 10
#       data1 = 3        data2 = 11
#       data1 = 4        data2 = 12
#       data1 = 5        data2 = 13
#       data1 = 6        data2 = 14
#       data1 = 7        data2 = 15

        由于随机化模式关闭了, 虽然对data1和data2的显式赋值全部违反约束但是并没有引发报错。同样,由于随机化模式关闭了,data1和data2被初始化为0了。

4. pre/post_randomize()在子类和父类中的调用顺序是什么?

        这个问题其实有点坑。基于以下实验代码来进行说明。child_trans1/2/3都继承于child_trans,在child_trans中定义了pre/post_randomize()。

        child_trans1中显式定义了pre/post_randomize(),但是其中没有显式调用super.pre/post_randomize()。child_trans2中显式定义了pre/post_randomize(),但是其中显式调用super.pre/post_randomize()。child_trans3中没有显式定义了pre/post_randomize()。        

//class
class trans;
  rand  bit [3:0] data1;
  randc bit [3:0] data2;
  
  constraint c_data1 { data1 >= 8;}
  constraint c_data2 { data2 <  8;}
  
  function void pre_randomize();
    $display("trans: called before randomization!");
  endfunction  

  function void post_randomize();
    $display("trans: called after randomization!");
  endfunction  
 
endclass
 
class child_trans1 extends trans;
  rand  bit [3:0] data3;
  randc bit [3:0] data4;
  
  constraint c_data3 { data3 >= 8;}
  constraint c_data4 { data4 <  8;}
  
  function void pre_randomize();
    $display("child_trans1: called before randomization!");
  endfunction  

  function void post_randomize();
    $display("child_trans1: called after randomization!");
  endfunction   
endclass
 
class child_trans2 extends trans;
  rand  bit [3:0] data5;
  randc bit [3:0] data6;
  
  constraint c_data5 { data5 >= 8;}
  constraint c_data6 { data6 <  8;}
  
  function void pre_randomize();
    super.pre_randomize();
    $display("child_trans2: called before randomization!");
  endfunction  

  function void post_randomize();
    super.post_randomize();
    $display("child_trans2: called after randomization!");
  endfunction   
endclass

class child_trans3 extends trans;
  rand  bit [3:0] data7;
  randc bit [3:0] data8;
  
  constraint c_data7 { data7 >= 8;}
  constraint c_data8 { data8 <  8;}
  
endclass
 
module test1;
  initial begin
    trans tr;
    tr = new();
    
    if(tr.randomize())
      $display("Randomization successful ! data1 = %0d \t data2 = %0d",tr.data1,tr.data2);
    else
      $display("Randomization failed ! ");    
  end
endmodule

module test2;
  initial begin
    child_trans1 ctr;
    ctr = new();
        
    void'(ctr.randomize());
    $display("\tdata1 = %0d \t data2 = %0d \t data3 = %0d \t data4 = %0d",
      ctr.data1,ctr.data2,ctr.data3,ctr.data4);
    
  end
endmodule

module test3;
  initial begin
    child_trans2 ctr;
    ctr = new();
    
    void'(ctr.randomize());
    $display("\tdata1 = %0d \t data2 = %0d \t data5 = %0d \t data5 = %0d",
      ctr.data1,ctr.data2,ctr.data5,ctr.data5);
  end
endmodule

module test4;
  initial begin
    child_trans3 ctr;
    ctr = new();
    
    void'(ctr.randomize());
    $display("\tdata1 = %0d \t data2 = %0d \t data7 = %0d \t data8 = %0d",
      ctr.data1,ctr.data2,ctr.data7,ctr.data8);
  end
endmodule

        编译以下代码文件并分别运行work.test2,work.test3,work.test4.

work.test2:

# child_trans1: called before randomization!
# child_trans1: called after randomization!

work.test3: 

# trans: called before randomization!
# child_trans2: called before randomization!
# trans: called after randomization!
# child_trans2: called after randomization!

 work.test4: 

# trans: called before randomization!
# trans: called after randomization!

        基于以上实验可以看出,pre/post_randomize()在类的继承中的调用行为与普通的systemverilog的类的方法的行为是一致的,即:

  • (1) 同名同参数时,子类方法覆盖父类方法,父类方法不会被调用
  • (2) 同名同参数时,如果希望父类方法得到调用,需要在子类方法中以super.methodname()的形式对父类方法进行显式调用
  • (3) 如果子类没有定义的话,缺省地会调用父类的方法

5. pre/post_randomize()是虚方法吗?

        答案是:它们不是虚方法(virtual method),但是它们以虚方法的方式工作(behave as virtual methods)。如果用户自定义类中,将pre/post_randomize()定义为virtual method的话,编译器会报错。比如说,定义类如下的话:

class trans;
  rand  bit [3:0] data1;
  randc bit [3:0] data2;
  
  constraint c_data1 { data1 >= 8;}
  constraint c_data2 { data2 <  8;}
  
  virtual function void pre_randomize();
    $display("trans: called before randomization!");
  endfunction  

  virtual function void post_randomize();
    $display("trans: called after randomization!");
  endfunction  
 
endclass

        用questasim编译命令vlog编译后报告如下错误: 

** Error: pre_post_randomize.sv(13): (vlog-2943) Invalid use of 'virtual' keyword for built-in function 'post_randomize'
.
** Error: pre_post_randomize.sv(9): (vlog-2943) Invalid use of 'virtual' keyword for built-in function 'pre_randomize'. 

6. 随机化的层次化

        考虑在SV验证平台中,test作为测试的顶层,在test中包含有generator用于生成transaction。

        首先,transaction中的成员可以定义为rand或者randc类型;

        其次,在generator中,可以根据需要进行数据或者参数的随机化处理,并且进一步根据这个随机化生成的数据或者参数影响或者控制transaction的成员的随机化

        最后,在test这一层可以控制generator的随机化处理。

        通过这种层次化的随机化处理,提供了灵活的随机化控制机制。比如说,在最初的smoke test中,只需要利用transaction中的随机化处理即可。但是,如果只有transaction中的随机化,就无法区分不同的test所需要的不同的随机化需求。

        如以下例子所示,generator在send_trans()中根据自己的成员随机化值控制trans的成员随机化,而generator的成员随机化又可以进一步在test层创建generator对象时进行控制。

  class trans;
    rand bit[31:0] data[];
    rand int ch_id;
    rand int pkt_id;
    rand int data_nidles;
    rand int pkt_nidles;
    ...
  endclass

  class generator;
    rand int pkt_id = -1;
    rand int ch_id = -1;
    rand int data_nidles = -1;
    rand int pkt_nidles = -1;
    rand int data_size = -1;
    rand int ntrans = 10;
    ...

    function new();
    ...
    endfunction
    task run();
      repeat(ntrans) send_trans();
    endtask

    // generate transaction and put into local mailbox
    task send_trans();
      chnl_trans req, rsp;
      req = new();
      assert(req.randomize with {local::ch_id >= 0 -> ch_id == local::ch_id; 
                      local::pkt_id >= 0 -> pkt_id == local::pkt_id;
                      local::data_nidles >= 0 -> data_nidles == local::data_nidles;
                      local::pkt_nidles >= 0 -> pkt_nidles == local::pkt_nidles;
                      local::data_size >0 -> data.size() == local::data_size; 
                     })
        else $fatal("[RNDFAIL] channel packet randomization failure!");
      this.pkt_id++;
      $display(req.sprint());
      assert(rsp.rsp)
        else $error("[RSPERR] %0t error response received!", $time);
    endtask
  endclass: generator

7. 约束的几种形式

  1. 权重约束 dist:有两种操作符::=n :/n 第一种表示每一个取值权重都是n,第二种表示每一个取值权重为n/num。

  2. 条件约束 if else 和->(case):if else 就是和正常使用一样;->通过前面条件满足后可以触发后面事件的发生。

  3. 范围约束inside:inside{[min:max]};范围操作符,也可以直接使用大于小于符号进行,但是不可以连续使用,如 min<wxm<max 这是错误的

 

8. Rand 和randc的区别

  • rand修饰符:rand修饰的变量,每次随机时,都在取值范围内随机取一个值,每个值被随机到的概率是一样的,就想掷骰子一样。

  • randc修饰符:randc表示周期性随机,即取值范围内所有可能的值都取到过后,才会重复取值。类似于无放回采样。打个比方说,从一副扑克牌中任取一张,每取完一张后不放回,接着取下一张,这样经过54次采样后这一副牌就取完了,这样构成一轮无放回采样。然后再重置为一副完整的牌,重复以上采样。

         

相关文章:

SystemVerilog: 仿真验证知识点点滴滴

常用数字设计仿真工具(QuestaSim,VCS,IUS,Verdi等)入门

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笨牛慢耕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值