当前位置:   article > 正文

【数字IC验证进阶】SystemVerilog的随机稳定性

随机稳定

前言

随机数生成器(RNG)是SystemVerilog语言及其各种验证方法中的重要组成部分。我们深入研究了线程和对象如何生成随机数,代码中对象和线程的排序如何影响随机化,以及在开发测试平台时需要注意的事项。

介绍

我为什么要关心随机稳定性?

作为基于约束的随机验证方法的一部分,在ASIC/FPGA开发过程中,每晚会运行一套测试,并且每晚的回归测试都会使用不同的种子运行。这样,每当测试使用不同的随机种子运行时,测试平台中的 $urandom, $urandom_range & (class) randomize()调用会生成不同的随机数,从而导致DUT的不同激励和配置。

这些回归测试中的任何测试失败都必须是可复现的,特别是如果失败结果证明是RTL错误。然后的处理流程是修复RTL并使用揭示错误的相同随机种子重新运行失败的测试,以验证是否修复了该错误。因此,您应该关注随机稳定性,因为为了成功重现那个失败的测试并验证RTL错误是否已修复,您需要理解随机稳定性和一个重要的属性,称为分层种子(Hierarchical Seeding)。

理解随机稳定性是实现基于约束的随机验证策略成功的关键。

什么是随机稳定性?

在上文中,我们已经确切地确定了重现测试失败的重要性。现在,为了做到这一点,测试平台必须展现可预测的RNG(随机数生成器)行为,即在程序块、线程和对象中生成的随机数序列应该对于给定的种子而言每次都是相同的。

随机稳定性基本上解释了在SystemVerilog中如何生成随机数以确保可重现性。它由4个属性描述:

  1. 对象稳定性(Object Stability)
  2. 线程稳定性(Thread Stability)
  3. 分层种子(Hierarchical Seeding)
  4. RNG初始化(Initialization RNG)

1.对象稳定性(Object Stability) 和 线程稳定性(Thread Stability)

要理解对象稳定性和线程稳定性,我们首先需要了解一个关键概念。

在SystemVerilog中,一切都是 “进程” (Process) – 程序块 (program-block)、线程(thread)、对象(object)、函数(function)或任务(task) 调用都是独立的进程,并且SystemVerilog进程的一个重要属性是它们每个都有独立的 RNG(随机数生成器)。

上面的句子让您有点头痛吗?别担心!这里有很多内容…
下面为 示例1.1 及其相应的 CONSOLE OUTPUT 。特别要注意类型为 process 的变量 pp 和 pt。

/* Example 1.1 */
program automatic test;
    class pkt;

        function int unsigned getrand();
            process local_proc;

            // Get handle to function's process
            local_proc = process::self();
            $display("class-function randstate %s",
                local_proc.get_randstate());
        endfunction
    endclass: pkt

    initial begin
        int unsigned var_a;
        pkt p;
        process pp, pt;
        string pp_randstate, pc_randstate, pt_randstate;

        // Get handle to program-block's process
        pp = process::self();
        // Get and print program-block's RNG's randstate
        pp_randstate = pp.get_randstate();
        $display("program-block randstate %s", pp_randstate);

        fork
        begin
            // Get handle to thread's process
            pt = process::self();
            // Get thread's internal RNG randstate
            pt_randstate = pt.get_randstate();
            $display("thread-block randstate %s",
                pt_randstate);
        end
        join

        p = new();
        // Classes have an in-built process defined.
        // .. You can get object's internal RNG's 
        // .. randstate by calling obj.get_randstate.
        pc_randstate = p.get_randstate();
        $display("class-object randstate %s", pc_randstate);

        // This function call is a process in itself.
        // .. Within this function we can fetch the 
        // .. process handle and print randstate
        p.getrand();
    end
endprogram
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
CONSOLE OUTPUT - Mentor Questa Sim
# program-block randstate MSe2aab990e149a42b970a1cd680bde03f
# thread-block randstate MS438ef57f498295b9ba42179530d3415c
# class-object randstate MS9d943356938f2b6cf29ab3e32f1fd461
# class-function randstate MSd3b01445af5da33c46529df6a5d82faa

CONSOLE OUTPUT - Synopsys VCS
program-block randstate 0000000000000000000000000000000000111010101110111011000011010010
thread-block randstate 0000000000000000000000000000000001011100110000001011100011110010
class-object randstate 00Z1ZZZ1Z0ZZ11XZX11Z1ZX1XZ1Z0X01XZZZZZXZXZXZZZZXXXXZZXZZXXZZXXZZ
class-function randstate 0Z110X0X0Z0XZ1XZZ111ZZ1ZZ11Z1XXZZXXXZXZZXXXZZZXZXXZZZZZZXZXXXZZZ
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这是我们看到的内容:

  1. Process是一个内置的静态类。
  2. 您可以通过在线程、程序块或函数中调用Process::self()来获取对该线程、程序块或函数的进程的句柄。
  3. 上文提到每个进程都有自己独立的内部RNG。RNG具有一个名为"random state"的属性,该状态决定了进程的RNG生成的下一个随机数。可以使用get_randstate()来查询RNG的当前状态。
  4. 这个"random state"由一个长字符串表示。这个字符串的外观是与实现相关的,即它在一个模拟器与另一个模拟器之间会有所不同。

这个示例的主要要点是线程和对象有它们自己的RNG。这使得对象和线程表现出确定性和稳定的行为,因为一个线程或对象内的$urandom和randomize()调用不会影响另一个线程或对象。如果有10个线程被fork出来,线程执行的顺序不会影响随机数的生成。

这种行为,其中一个对象实例中的randomize() 方法与其他实例中的 randomize() 调用是独立的,称为对象稳定性。类似地,线程稳定性是指一个线程内的 $urandom()、$urandom_range() 或 std::randomize() 调用不会影响另一个线程,因此线程执行的顺序不会影响随机数的生成。

2. 分层种子(Hierarchical Seeding)

线程和对象的RNG(即每个进程)是分层种子的。当创建一个线程或new一个对象时,其内部RNG的随机状态将使用父块的RNG的下一个随机值作为种子进行初始化。让我们看一个例子。

示例 2.1显示在new对象p之前生成了5个随机数。 示例 2.2显示了在new对象之后生成了相同的5个随机数的for循环。观察控制台输出-你看到了一个模式吗?在示例 2.2中,第一个随机数(0x1c211259)被用作对象p的内部RNG的种子,之后的for循环继续产生相同的数列。这就是分层种子的作用。

/* Example 2.1 */
program automatic test;
    class pkt;
        rand logic [31:0] saddr, daddr, etype;
    endclass: pkt

    initial begin
        pkt p, p_th;
        int unsigned var_a, var_b, var_c;

        for (int ii=0; ii<5; ii++) begin
            var_a = $urandom();
            $display("urandom before object new 0x%x", var_a);
        end
        p = new(); // Object new after for-loop
    end
endprogram
/* Example 2.2 */
program automatic test;
    class pkt;
        rand logic [31:0] saddr, daddr, etype;
    endclass: pkt

    initial begin
        pkt p, p_th;
        int unsigned var_a, var_b, var_c;

        p = new(); // Object new before for-loop
        for (int ii=0; ii<5; ii++) begin
            var_a = $urandom();
            $display("urandom before object new 0x%x", var_a);
        end  
    end
endprogram
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
CONSOLE OUTPUT
/* Example 2.1 */
# urandom before object new 0x1c211259
# urandom before object new 0x762e4948
# urandom before object new 0x3e10f810
# urandom before object new 0x5fd6b9d8
# urandom before object new 0x1bfad73e

/* Example 2.2 */
# urandom after object new 0x762e4948
# urandom after object new 0x3e10f810
# urandom after object new 0x5fd6b9d8
# urandom after object new 0x1bfad73e
# urandom after object new 0xdae9cf76
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

让我们来看一个带有线程的例子。与 示例 2.1的输出相比, 示例 2.3中的第一个随机数由程序块的RNG(0x1c211259)用作对象p的RNG的种子,第二个随机数(0x762e4948)用作线程的内部RNG的种子。在线程加入后,程序块的for循环继续按序列产生下一个随机数(0x3e10f810)。同时,注意线程内部的for循环不会影响线程外部的程序块的for循环。这就是随机稳定性的作用。

/* Example 2.3 */
program automatic test;
    class pkt;
        rand logic [31:0] saddr, daddr, etype;
    endclass: pkt

    initial begin
        pkt p, p_th;
        int unsigned var_a, var_b, var_c;
        // 1. object new
        p = new(); 
        p.randomize();
        $display("p->saddr 0x%x daddr 0x%x etype 0x%x", p.saddr, p.daddr, p.etype);
        // 2. fork thread
        fork begin
            for (int ii=0; ii<5; ii++) begin
                var_b = $urandom();
                $display("thread-block urandom 0x%x", var_b);
            end
        end join
        // 3. program for-loop
        for (int ii=0; ii<5; ii++) begin
            var_a = $urandom();
            $display("program-block urandom  0x%x", var_a);
        end
    end
endprogram
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
CONSOLE OUTPUT
# p->saddr 0x1ff13f40 daddr 0xd87f903a etype 0xd2b1ebf1
#
# thread-block urandom 0x294f77ba
# thread-block urandom 0x60323223
# thread-block urandom 0x4279ce38
# thread-block urandom 0xb50c1a80
# thread-block urandom 0x6bc779b7
#
# program-block urandom  0x3e10f810
# program-block urandom  0x5fd6b9d8
# program-block urandom  0x1bfad73e
# program-block urandom  0xdae9cf76
# program-block urandom  0xdcf9d962
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

现在我们了解了分层种子,我们可以得出以下结论:

  • 为了重新创建我们的失败测试,对象和线程的创建必须按照之前的顺序进行。
  • 在测试平台中添加代码可能会改变生成的随机数序列,因为您改变了分层种子的顺序(通过在旧代码之间创建新对象和线程)。
  • 因此,为了重新创建测试模拟,必须在代码块的末尾添加新的线程、对象和randomize方法。

3. 初始化RNG

下一个显而易见的问题是,如果线程和对象由其父级种子,那么程序块是由谁种子的呢?程序块具有称为初始化RNG的东西。您可以将其视为"根RNG"。这些由Synopsys VCS的命令行参数+ntb_random_seed和 Mentor Graphics Questasim 的 -sv_seed进行种子。

除了程序块之外,每个模块实例、接口实例和包实例都有一个初始化RNG。

简而言之

以火车做类比

为了确保您已经理解了这个概念-随机稳定性的4个属性可以与以下火车类比进行比较。如果将随机数生成器(RNG)看作是一辆火车,它生成的随机数序列看作是它的轨道,那么:

  1. 对于给定的初始种子,RNG火车生成相同的轨道(或序列)随机数。
  2. SystemVerilog中的每个线程和对象都有自己的RNG(火车)。
  3. 当创建一个线程或new一个对象时,您正在创建一个新的RNG火车,并且该火车会使用其父级火车的下一个随机数进行初始化。
    Random Stability Train Analogy
复现 fail 的 test
  • 为了重新创建测试失败的程序,必须保持程序、对象和线程的稳定性。
  • 只要对象的创建、线程的创建和随机数的生成顺序与之前相同,就可以实现程序、对象和线程的稳定性。
  • 为了保持随机数的稳定性,可以在现有对象创建后创建新的对象、线程和随机数。
    如果测试失败是由于RTL错误导致的,由于随机稳定性的变化,更改RTL以修复错误不会影响我们重新创建失败的能力。但是,如果必须修改测试序列或测试平台,应注意保持程序、对象和线程的稳定性。- 将任何新的类成员变量添加到末尾- 如果可能,将新的对象或线程推迟到父类或程序块的末尾。

种子(Seeding)

现在我们了解了SV中的所有内容都是进程,每个进程都有一个内部RNG,我们可以玩弄这个RNG的随机状态。

有两种方法可以为这些进程的内部RNG提供种子:

surandom()

示例 4.1应该是不言自明的。您可以在线程内部使用process::srandom(seed)重新为对象或线程的RNG提供种子,或者对于对象调用obj.srandom(seed)。

/* Example 4.1 */
program automatic test;
    class pkt;
        rand int randvar;
    endclass:pkt

    initial begin
        int unsigned var_a;
        process pp, pt, pc;
        string pp_randstate, pt_randstate, pc_randstate;
        pkt p;

        fork
        begin
            pt = process::self();
            $display("thread-block randstate %s", pt.get_randstate());
            pt.srandom(1234);
            $display("Changing thread-block randstate %s", pt.get_randstate());
            for (int ii=0; ii<5; ii++) begin
                var_a = $urandom();
                $display("var_a %d", var_a);
            end
        end
        join
        p = new();
        $display("object's randstate %s", p.get_randstate());
        pt.srandom(6666);
        $display("Changing object's randstate %s", p.get_randstate());
        for(int ii=0; ii<5; ii++) begin
            p.randomize();
            $display("p: p.randvar %d", p.randvar);
        end
    end
endprogram
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
CONSOLE OUTPUT:
# thread-block randstate MS438ef57f498295b9ba42179530d3415c
# Changing thread-block randstate MS72dfdf2597f7d0779f2e5602c960a1e5
# var_a 3151364325
# var_a  797357057
# var_a 1347950301
# var_a 4120409912
# var_a 2561059293
# object's randstate MS9d943356938f2b6cf29ab3e32f1fd461
# Changing object's randstate MS9d943356938f2b6cf29ab3e32f1fd461
# p: p.randvar  1042171828
# p: p.randvar   438788930
# p: p.randvar  1389555781
# p: p.randvar  1120102834
# p: p.randvar   820092897
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

set_randstate()
这是额外的奖励。

示例 1.1中,我们看到了如何使用get_randstate()获取进程的RNG的randstate。您还可以使用 set_randstate() 设置进程的RNG 的 randstate。

示例 3.1中,我们保持简单-首先在程序块内生成5个随机数,然后在线程内生成5个随机数。这给我们了10个不同的随机数。

现在让我们玩弄一下线程的RNG,并使用 set_randstate() 改变它的状态。在示例3.2中,我们在一开始记录了程序块的RNG randstate,然后在生成5个数字之前将线程的RNG设置为该状态。看看会发生什么-线程生成的随机数与程序块相同!

这基本上确认了通过randomize方法调用($urandom、$urandom_range、randomize)返回的随机数仅取决于该时刻进程的随机数生成器的状态。

/* Example 3.1 */
program automatic test;
    initial begin
        int unsigned var_a;
        process pp, pt, pc;
        string pp_randstate, pt_randstate, pc_randstate;

        // Record the state of the program block's RNG right
        // at the beginning of the initial block
        pp = process::self();
        pp_randstate = pp.get_randstate();
        $display("program-block randstate %s", pp_randstate);

        // Generate 5 random numbers using the program-block's
        // Random Number Generator (RNG)
        for (int ii=0; ii<5; ii++) begin
            var_a = $urandom();
            $display("program-block var_a %d", var_a);
        end

        fork
        begin
            // Since 5 random numbers were generated using
            // the program-block's RNG, 
            // this thread's RNG state gets initialized by the
            // 6th random number of the program-block's RNG
            pt = process::self();
            pt_randstate = pt.get_randstate();
            $display("thread-block randstate %s", pt_randstate);
            for (int ii=0; ii<5; ii++) begin
                var_a = $urandom();
                $display("thread var_a %d", var_a);
            end
        end
        join
    end
endprogram
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
/* Example 3.2 */
program automatic test;
    class pkt;
        rand int randvar;
    endclass:pkt

    initial begin
        int unsigned var_a;
        process pp, pt, pc;
        string pp_randstate, pt_randstate, pc_randstate;
        pkt p;

        // Record the state of the program block's RNG
        // at the beginning of the initial block
        pp = process::self();
        pp_randstate = pp.get_randstate();
        $display("program-block randstate %s", pp_randstate);

        // Generate 5 random numbers using the program-block's
        // Random Number Generator (RNG)
        for (int ii=0; ii<5; ii++) begin
            var_a = $urandom();
            $display("program-block var_a %d", var_a);
        end

        fork
        begin
            // This thread's RNG state gets initialized by the
            // 6th random number of the program-block's RNG,
            // but we over-write this RNG's state to
            // "pp_randstate" which was the program-block's
            // state at the start of execution.
            // Observe the output the 5 random numbers generated 
            // below should be the same as the above for-loop.
            pt = process::self();
            pt.set_randstate(pp_randstate);
            pt_randstate = pt.get_randstate();
            $display("thread-block randstate %s", pt_randstate);
            for (int ii=0; ii<5; ii++) begin
                var_a = $urandom();
                $display("thread var_a %d", var_a);
            end
        end
        join 
    end
endprogram
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
/* Example 3.1 Output */
# program-block randstate MS0d46fe96c77f6e86d3ff4d7c45fa86ee
# program-block var_a  750503525
# program-block var_a  354101471
# program-block var_a 1016169009
# program-block var_a 1500937797
# program-block var_a 3106080629
# thread-block randstate MSb0eb5ff259752d3100c01bb5dd17e861
# thread var_a 1381377367
# thread var_a  335209292
# thread var_a 4170423523
# thread var_a  626467677
# thread var_a 1536526296
/* Example 3.2 Output */
# program-block randstate MS0d46fe96c77f6e86d3ff4d7c45fa86ee
# program-block var_a  750503525
# program-block var_a  354101471
# program-block var_a 1016169009
# program-block var_a 1500937797
# program-block var_a 3106080629
# thread-block randstate MS0d46fe96c77f6e86d3ff4d7c45fa86ee
# thread var_a  750503525
# thread var_a  354101471
# thread var_a 1016169009
# thread var_a 1500937797
# thread var_a 3106080629
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

随着这一点,我们已经基本上涵盖了关于随机稳定性的所有讨论内容。

以上内容均来自下面的网址,收获颇丰,特此翻译分享给大家,https://www.systemverilog.io/verification/random-stability/
而且推荐给大家这个网站,是一个很好的学习ASIC/SoC设计和验证的网站 https://www.systemverilog.io/

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/418673
推荐阅读
相关标签
  

闽ICP备14008679号