UVM中的sequence

第6章 UVM中的sequence

UVM 的 sequence 机制:从 driver 中剥离激励产生、sequence 的启动与仲裁、各类宏的实现、p_sequencer、嵌套与 virtual sequence、在 sequence 中使用 config_db、response 反馈机制,以及 sequence library

6.1 sequence基础

6.1.1 从driver中剥离激励产生功能

最开始,激励最初产生在 driver 中,后来产生在 sequence 中。为什么会有这个过程呢?

最开始时,driver 的 main_phase 是这样的:

清单 6-1

systemverilog 复制代码
task my_driver::main_phase(uvm_phase phase); 
  my_transaction tr; 
  phase.raise_objection(this); 
  for(int i = 0; i < 10; i++) begin  
    tr = new; 
    assert(tr.randomize); 
    drive_one_pkt(tr); 
  end 
  phase.drop_objection(this); 
endtask 

如果只是施加上述一种激励,这样是可以的。但当要对 DUT 施加不同的激励时,那应该怎么办呢?上述代码中是施加了正确的包,而下一次测试中要在第 9 个 transaction 中加入 CRC 错误的包,那么可以这么写:

清单 6-2

systemverilog 复制代码
task my_driver::main_phase(uvm_phase phase); 
  my_transaction tr; 
  phase.raise_objection(this); 
  for(int i = 0; i < 10; i++) begin  
    tr = new; 
    if(i == 8) 
      assert(tr.randomize with {tr.crc_err == 1;}); 
    else 
      assert(tr.randomize); 
    drive_one_pkt(tr); 
  end 
  phase.drop_objection(this); 
endtask 

这就相当于将整个 main_phase 重新写了一遍。如果现在有了新的需求,需要再测一个超长包,则需要再次改写 main_phase,也就是说,每多测一种情况,就要多改写一次 main_phase。如果经常改写某个任务或者函数,那么就很容易将之前对的地方改错。所以说,这种方法是不可取的,因为它的可扩展性太差,会经常带来错误。

仔细观察 main_phase,其实只有从 tr=new 语句至 drive_one_pkt 之间的语句在变。有没有什么方法可以将这些语句从 main_phase 中独立出来呢?最好的方法就是在不同的测试用例中决定这几行语句的内容。这种想法中已经包含了"激励的产生与驱动的分离"这个观点。drive_one_pkt 是驱动,这是 driver 应该做的事情,但是像"产生什么样的包、如何产生"等这些事情应该从 driver 中独立出去。

要实现上述目标,可以使用一个函数来实现:

清单 6-3

systemverilog 复制代码
function void gen_pkt(ref my_transaction tr); 
  tr = new;  
  assert(tr.randomize); 
endfunction 

task my_driver::main_phase(uvm_phase phase); 
  my_transaction tr; 
  bit send_crc_err = 0; 
  phase.raise_objection(this); 
  for(int i = 0; i < 10; i++) begin  
    gen_pkt(tr); 
    drive_one_pkt(tr); 
  end 
  phase.drop_objection(this); 
endtask 

如上所示,可以定义一个产生正常包的 gen_pkt 函数,但是如何定义一个 CRC 错误包的函数呢?难道像下面这样吗?

清单 6-4

systemverilog 复制代码
function void gen_pkt(ref my_transaction tr); 
  tr = new;  
  assert(tr.randomize with {crc_err == 1;}); 
endfunction 

这样带来的一个最大的问题就是 gen_pkt 函数的重复定义,显然这样是不允许的。为了避免重复定义,有两种策略:第一种是使用虚函数。将代码清单 6-3 中的 gen_pkt 定义为 virtual 类型,然后在建造 CRC 错误的测试用例时,从 my_driver 派生一个新的 crc_err_driver,并重载 gen_pkt 函数。但是这样新的问题又出现了,如何在这个测试用例中实例化这个新的 driver 呢?似乎只能重新定义一个 my_agent ,为了实例化这个新的 agent,又只能重新定义一个 my_env。这种解决方式显然是不可取的。第二种解决方式是使定义的函数的名字是不一样的,但是在 driver 的 main_phase 中又无法执行这种具有不同名字的函数。

这是一个相当难的问题,单纯用 SystemVerilog 提供的一些接口是根本无法实现的。UVM 为了解决这个问题,引入了 sequence 机制,在解决的过程中还使用了 factory 机制、config 机制。使用 sequence 机制之后,在不同的测试用例中,将不同的 sequence 设置成 sequencer 的 main_phasedefault_sequence。当 sequencer 执行到 main_phase 时,发现有 default_sequence,那么它就启动 sequence。

仔细回想上面的过程,sequencer 启动 sequence 并执行的过程就相当于之前的 gen_pkt,只是调用的位置从 driver 变到 sequencer。sequencer 将 sequence 产生的 transaction 交给 driver,这其实与在 driver 里面调用 gen_pkt 没有本质的区别。

6.1.2 sequence的启动与执行

当完成一个 sequence 的定义后,可以使用 start 任务将其启动:

清单 6-5

systemverilog 复制代码
my_sequence my_seq; 
my_seq = my_sequence::type_id::create("my_seq"); 
my_seq.start(sequencer); 

除了直接启动之外,还可以使用 default_sequence 启动。事实上 default_sequence 会调用 start 任务,它有两种调用方式,其中一种是前文已经介绍过的:

清单 6-6

systemverilog 复制代码
uvm_config_db#(uvm_object_wrapper)::set(this,  
                                "env.i_agt.sqr.main_phase",  
                                "default_sequence",  
                                case0_sequence::type_id::get()); 

另外一种方式是先实例化要启动的 sequence,之后再将其通过 default_sequence 启动:

清单 6-7

systemverilog 复制代码
文件:src/ch6/section6.1/6.1.2/my_case0.sv 
 41 function void my_case0::build_phase(uvm_phase phase); 
 42   case0_sequence cseq; 
 43   super.build_phase(phase); 
 44 
 45   cseq = new("cseq"); 
 46   uvm_config_db#(uvm_sequence_base)::set(this, 
 47                                     "env.i_agt.sqr.main_phase", 
 48                                     "default_sequence", 
 49                                     cseq); 
 50 endfunction 

当一个 sequence 启动后会自动执行 sequence 的 body 任务。其实,除了 body 外,还会自动调用 sequence 的 pre_bodypost_body

清单 6-8

systemverilog 复制代码
文件:src/ch6/section6.1/6.1.2/my_case0.sv 
  3 class case0_sequence extends uvm_sequence #(my_transaction); 
... 
 10   virtual task pre_body(); 
 11     `uvm_info("sequence0", "pre_body is called!!!", UVM_LOW) 
 12   endtask 
 13 
 14   virtual task post_body(); 
 15     `uvm_info("sequence0", "post_body is called!!!", UVM_LOW) 
 16   endtask 
 17 
 18   virtual task body(); 
... 
 21     #100; 
 22     `uvm_info("sequence0", "body is called!!!", UVM_LOW) 
... 
 25   endtask 
 26 
 27   `uvm_object_utils(case0_sequence) 
 28 endclass 

上述的 sequence 在执行时,会打印出:

text 复制代码
# UVM_INFO my_case0.sv(11) @ 0: uvm_test_top.env.i_agt.sqr@@cseq [sequence0] pre_body is called!!! 
# UVM_INFO my_case0.sv(22) @ 100000: uvm_test_top.env.i_agt.sqr@@cseq [sequence0] body is called!!! 
# UVM_INFO my_case0.sv(15) @ 100000: uvm_test_top.env.i_agt.sqr@@cseq [sequence0] post_body is called!!! 

6.2 sequence的仲裁机制

6.2.1 在同一sequencer上启动多个sequence

在前文所有的例子中,同一时刻,在同一 sequencer 上只启动了一个 sequence。事实上,UVM 支持同一时刻在同一 sequencer 上启动多个 sequence。

my_sequencer 上同时启动了两个 sequence:sequence1sequence2,代码如下所示:

清单 6-9

systemverilog 复制代码
文件:src/ch6/section6.2/6.2.1/no_pri/my_case0.sv 
 57 task my_case0::main_phase(uvm_phase phase); 
 58   sequence0 seq0; 
 59   sequence1 seq1; 
 60 
 61   seq0 = new("seq0"); 
 62   seq0.starting_phase = phase; 
 63   seq1 = new("seq1"); 
 64   seq1.starting_phase = phase; 
 65   fork 
 66     seq0.start(env.i_agt.sqr); 
 67     seq1.start(env.i_agt.sqr); 
 68   join 
 69 endtask 

其中 sequence0 的定义为:

清单 6-10

systemverilog 复制代码
文件:src/ch6/section6.2/6.2.1/no_pri/my_case0.sv 
  3 class sequence0 extends uvm_sequence #(my_transaction); 
... 
 10   virtual task body(); 
... 
 13     repeat (5) begin 
 14       `uvm_do(m_trans) 
 15       `uvm_info("sequence0", "send one transaction", UVM_MEDIUM) 
 16     end 
 17     #100; 
... 
 20   endtask 
 21 
 22   `uvm_object_utils(sequence0) 
 23 endclass 

sequence1 的定义为:

清单 6-11

systemverilog 复制代码
文件:src/ch6/section6.2/6.2.1/no_pri/my_case0.sv 
 25 class sequence1 extends uvm_sequence #(my_transaction); 
... 
 32   virtual task body(); 
... 
 35     repeat (5) begin 
 36       `uvm_do_with(m_trans, {m_trans.pload.size < 500;}) 
 37       `uvm_info("sequence1", "send one transaction", UVM_MEDIUM) 
 38     end 
 39     #100; 
... 
 42   endtask 
 43 
 44   `uvm_object_utils(sequence1) 
 45 endclass 

运行如上代码后,会显示两个 sequence 交替产生 transaction:

text 复制代码
# UVM_INFO my_case0.sv(15) @ 85900: uvm_test_top.env.i_agt.sqr@@seq0 [sequence0] send one transaction 
# UVM_INFO my_case0.sv(37) @ 112500: uvm_test_top.env.i_agt.sqr@@seq1 [sequence1] send one transaction 
# UVM_INFO my_case0.sv(15) @ 149300: uvm_test_top.env.i_agt.sqr@@seq0 [sequence0] send one transaction 
# UVM_INFO my_case0.sv(37) @ 200500: uvm_test_top.env.i_agt.sqr@@seq1 [sequence1] send one transaction 
# UVM_INFO my_case0.sv(15) @ 380700: uvm_test_top.env.i_agt.sqr@@seq0 [sequence0] send one transaction 
# UVM_INFO my_case0.sv(37) @ 436500: uvm_test_top.env.i_agt.sqr@@seq1 [sequence1] send one transaction 

sequencer 根据什么选择使用哪个 sequence 的 transaction 呢?这是 UVM 的 sequence 机制中的仲裁问题。对于 transaction 来说,存在优先级的概念,通常来说,优先级越高越容易被选中。当使用 uvm_do 或者 uvm_do_with 宏时,产生的 transaction 的优先级是默认的优先级,即 -1。可以通过 uvm_do_priuvm_do_pri_with 改变所产生的 transaction 的优先级:

清单 6-12

systemverilog 复制代码
文件:src/ch6/section6.2/6.2.1/item_pri/my_case0.sv 
  3 class sequence0 extends uvm_sequence #(my_transaction); 
... 
 10   virtual task body(); 
... 
 13     repeat (5) begin 
 14       `uvm_do_pri(m_trans, 100) 
 15       `uvm_info("sequence0", "send one transaction", UVM_MEDIUM) 
 16     end 
 17     #100; 
... 
 20   endtask 
... 
 23 endclass 
 24 
 25 class sequence1 extends uvm_sequence #(my_transaction); 
... 
 32   virtual task body(); 
... 
 35     repeat (5) begin 
 36       `uvm_do_pri_with(m_trans, 200, {m_trans.pload.size < 500;}) 
 37       `uvm_info("sequence1", "send one transaction", UVM_MEDIUM) 
 38     end 
... 
 42   endtask 
... 
 45 endclass 

uvm_do_priuvm_do_pri_with 的第二个参数是优先级,这个数值必须是一个大于等于 -1 的整数。数字越大,优先级越高。

由于 sequence1 中 transaction 的优先级较高,所以按照预期,先选择 sequence1 产生的 transaction。当 sequence1 的 transaction 全部生成完毕后,再产生 sequence0 的 transaction。但是运行上述代码,发现并没有如预期的那样,而是 sequence0sequence1 交替产生 transaction。这是因为 sequencer 的仲裁算法有很多种:

清单 6-13

systemverilog 复制代码
来源:UVM 源代码 
  SEQ_ARB_FIFO, 
  SEQ_ARB_WEIGHTED, 
  SEQ_ARB_RANDOM, 
  SEQ_ARB_STRICT_FIFO, 
  SEQ_ARB_STRICT_RANDOM, 
  SEQ_ARB_USER 

在默认情况下 sequencer 的仲裁算法是 SEQ_ARB_FIFO。它会严格遵循先入先出的顺序,而不会考虑优先级。SEQ_ARB_WEIGHTED 是加权的仲裁;SEQ_ARB_RANDOM 是完全随机选择;SEQ_ARB_STRICT_FIFO 是严格按照优先级的,当有多个同一优先级的 sequence 时,按照先入先出的顺序选择;SEQ_ARB_STRICT_RANDOM 是严格按照优先级的,当有多个同一优先级的 sequence 时,随机从最高优先级中选择;SEQ_ARB_USER 则是用户可以自定义一种新的仲裁算法。

因此,若想使优先级起作用,应该设置仲裁算法为 SEQ_ARB_STRICT_FIFO 或者 SEQ_ARB_STRICT_RANDOM

清单 6-14

systemverilog 复制代码
文件:src/ch6/section6.2/6.2.1/item_pri/my_case0.sv 
 57 task my_case0::main_phase(uvm_phase phase); 
... 
 65   env.i_agt.sqr.set_arbitration(SEQ_ARB_STRICT_FIFO); 
 66   fork 
 67     seq0.start(env.i_agt.sqr); 
 68     seq1.start(env.i_agt.sqr); 
 69   join 
 70 endtask 

经过如上的设置后,会发现直到 sequence1 发送完 transaction 后,sequence0 才开始发送。

除 transaction 有优先级外,sequence 也有优先级的概念。可以在 sequence 启动时指定其优先级:

清单 6-15

systemverilog 复制代码
文件:src/ch6/section6.2/6.2.1/sequence_pri/my_case0.sv 
 57 task my_case0::main_phase(uvm_phase phase); 
... 
 65   env.i_agt.sqr.set_arbitration(SEQ_ARB_STRICT_FIFO); 
 66   fork 
 67     seq0.start(env.i_agt.sqr, null, 100); 
 68     seq1.start(env.i_agt.sqr, null, 200); 
 69   join 
 70 endtask 

start 任务的第一个参数是 sequencer,第二个参数是 parent sequence,可以设置为 null,第三个参数是优先级,如果不指定则此值为 -1,它同样不能设置为一个小于 -1 的数字。

使用代码清单 6-10 中的 sequence0 和代码清单 6-11 中的 sequence1,即不在 uvm_do 系列宏中指定优先级。运行上述代码,会发现 sequence1 中的 transaction 完全发送完后才发送 sequence0 中的 transaction。所以,对 sequence 设置优先级的本质即设置其内产生的 transaction 的优先级。

6.2.2 sequencer的lock操作

当多个 sequence 在一个 sequencer 上同时启动时,每个 sequence 产生出的 transaction 都需要参与 sequencer 的仲裁。那么考虑这样一种情况,某个 sequence 比较奇特,一旦它要执行,那么它所有的 transaction 必须连续地交给 driver,如果中间夹杂着其他 sequence 的 transaction,就会发生错误。要解决这个问题,可以对此 sequence 赋予较高的优先级。

但是假如有其他 sequence 有更高的优先级呢?所以这种解决方法并不科学。在 UVM 中可以使用 lock 操作来解决这个问题。

所谓 lock,就是 sequence 向 sequencer 发送一个请求,这个请求与其他 sequence 发送 transaction 的请求一同被放入 sequencer 的仲裁队列中。当其前面的所有请求被处理完毕后,sequencer 就开始响应这个 lock 请求,此后 sequencer 会一直连续发送此 sequence 的 transaction,直到 unlock 操作被调用。从效果上看,此 sequencer 的所有权并没有被所有的 sequence 共享,而是被申请 lock 操作的 sequence 独占了。一个使用 lock 操作的 sequence 为:

清单 6-16

systemverilog 复制代码
文件:src/ch6/section6.2/6.2.2/one_lock/my_case0.sv 
 25 class sequence1 extends uvm_sequence #(my_transaction); 
... 
 32   virtual task body(); 
... 
 35     repeat (3) begin 
 36       `uvm_do_with(m_trans, {m_trans.pload.size < 500;}) 
 37       `uvm_info("sequence1", "send one transaction", UVM_MEDIUM) 
 38     end 
 39     lock(); 
 40     `uvm_info("sequence1", "locked the sequencer ", UVM_MEDIUM) 
 41     repeat (4) begin 
 42       `uvm_do_with(m_trans, {m_trans.pload.size < 500;}) 
 43       `uvm_info("sequence1", "send one transaction", UVM_MEDIUM) 
 44     end 
 45     `uvm_info("sequence1", "unlocked the sequencer ", UVM_MEDIUM) 
 46     unlock(); 
 47     repeat (3) begin 
 48       `uvm_do_with(m_trans, {m_trans.pload.size < 500;}) 
 49       `uvm_info("sequence1", "send one transaction", UVM_MEDIUM) 
 50     end 
... 
 54   endtask 
... 
 57 endclass 

将此 sequence1 与代码清单 6-10 中的 sequence0 使用代码清单 6-9 的方式在 env.i_agt.sqr 上启动,会发现:在 lock 语句前,sequence0sequence1 交替产生 transaction;在 lock 语句后,一直发送 sequence1 的 transaction,直到 unlock 语句被调用后,sequence0sequence1 又开始交替产生 transaction。

如果两个 sequence 都试图使用 lock 任务来获取 sequencer 的所有权则会如何呢?答案是先获得所有权的 sequence 在执行完毕后才会将所有权交还给另外一个 sequence。

清单 6-17

systemverilog 复制代码
文件:src/ch6/section6.2/6.2.2/dual_lock/my_case0.sv 
  3 class sequence0 extends uvm_sequence #(my_transaction); 
... 
 10   virtual task body(); 
... 
 13     repeat (2) begin 
 14       `uvm_do(m_trans) 
 15       `uvm_info("sequence0", "send one transaction", UVM_MEDIUM) 
 16     end 
 17     lock(); 
 18     repeat (5) begin 
 19       `uvm_do(m_trans) 
 20       `uvm_info("sequence0", "send one transaction", UVM_MEDIUM) 
 21     end 
 22     unlock(); 
 23     repeat (2) begin 
 24       `uvm_do(m_trans) 
 25       `uvm_info("sequence0", "send one transaction", UVM_MEDIUM) 
 26     end 
 27     #100; 
... 
 30   endtask 
 31 
 32   `uvm_object_utils(sequence0) 
 33 endclass 

将上述 sequence0 与代码清单 6-16 中的 sequence1 同时在 env.i_agt.sqr 上启动,会发现 sequence0 先获得 sequencer 的所有权,在 unlock 函数被调用前,一直发送 sequence0 的 transaction。在 unlock 被调用后,sequence1 获得 sequencer 的所有权,之后一直发送 sequence1 的 transaction,直到 unlock 函数被调用。

6.2.3 sequencer的grab操作

lock 操作一样,grab 操作也用于暂时拥有 sequencer 的所有权,只是 grab 操作比 lock 操作优先级更高。lock 请求是被插入 sequencer 仲裁队列的最后面,等到它时,它前面的仲裁请求都已经结束了。grab 请求则被放入 sequencer 仲裁队列的最前面,它几乎是一发出就拥有了 sequencer 的所有权:

清单 6-18

systemverilog 复制代码
文件:src/ch6/section6.2/6.2.3/my_case0.sv 
 25 class sequence1 extends uvm_sequence #(my_transaction); 
... 
 32   virtual task body(); 
... 
 35     repeat (3) begin 
 36       `uvm_do_with(m_trans, {m_trans.pload.size < 500;}) 
 37       `uvm_info("sequence1", "send one transaction", UVM_MEDIUM) 
 38     end 
 39     grab(); 
 40     `uvm_info("sequence1", "grab the sequencer ", UVM_MEDIUM) 
 41     repeat (4) begin 
 42       `uvm_do_with(m_trans, {m_trans.pload.size < 500;}) 
 43       `uvm_info("sequence1", "send one transaction", UVM_MEDIUM) 
 44     end 
 45     `uvm_info("sequence1", "ungrab the sequencer ", UVM_MEDIUM) 
 46     ungrab(); 
 47     repeat (3) begin 
 48       `uvm_do_with(m_trans, {m_trans.pload.size < 500;}) 
 49       `uvm_info("sequence1", "send one transaction", UVM_MEDIUM) 
 50     end 
... 
 54   endtask 
 55 
 56   `uvm_object_utils(sequence1) 
 57 endclass 

如果两个 sequence 同时试图使用 grab 任务获取 sequencer 的所有权将会如何呢?这种情况与两个 sequence 同时试图调用 lock 函数一样,在先获得所有权的 sequence 执行完毕后才会将所有权交还给另外一个试图所有权的 sequence。

如果一个 sequence 在使用 grab 任务获取 sequencer 的所有权前,另外一个 sequence 已经使用 lock 任务获得了 sequencer 的所有权则会如何呢?答案是 grab 任务会一直等待 lock 的释放。grab 任务还是比较讲文明的,虽然它会插队,但是绝不会打断别人正在进行的事情。

6.2.4 sequence的有效性

当有多个 sequence 同时在一个 sequencer 上启动时,所有的 sequence 都参与仲裁,根据算法决定哪个 sequence 发送 transaction。仲裁算法是由 sequencer 决定的,sequence 除了可以在优先级上进行设置外,对仲裁的结果无能为力。

通过 lock 任务和 grab 任务,sequence 可以独占 sequencer,强行使 sequencer 发送自己产生的 transaction。同样的,UVM 也提供措施使 sequence 可以在一定时间内不参与仲裁,即令此 sequence 失效。

sequencer 在仲裁时,会查看 sequence 的 is_relevant 函数的返回结果。如果为 1,说明此 sequence 有效,否则无效。因此可以通过重载 is_relevant 函数来使 sequence 失效:

清单 6-19

systemverilog 复制代码
文件:src/ch6/section6.2/6.2.4/is_relevant/my_case0.sv 
  3 class sequence0 extends uvm_sequence #(my_transaction); 
  4   my_transaction m_trans; 
  5   int num; 
  6   bit has_delayed; 
... 
 14   virtual function bit is_relevant(); 
 15     if((num >= 3)&&(!has_delayed)) return 0; 
 16     else return 1; 
 17   endfunction 
 18 
 19   virtual task body(); 
... 
 22     fork 
 23       repeat (10) begin 
 24           num++; 
 25           `uvm_do(m_trans) 
 26           `uvm_info("sequence0", "send one transaction", UVM_MEDIUM) 
 27         end 
 28         while(1) begin 
 29           if(!has_delayed) begin 
 30             if(num >= 3) begin 
 31                 `uvm_info("sequence0", "begin to delay", UVM_MEDIUM) 
 32                 #500000; 
 33                 has_delayed = 1'b1; 
 34                 `uvm_info("sequence0", "end delay", UVM_MEDIUM) 
 35                 break; 
 36               end 
 37               else 
 38                 #1000; 
 39           end 
 40        end 
 41     join 
... 
 45   endtask 
... 
 48 endclass 

这个 sequence 在发送了 3 个 transaction 后开始变为无效,延时 500000 时间单位后又开始有效。将此 sequence 与代码清单 6-11 中的 sequence1 同时启动,会发现在失效前 sequence0sequence1 交替发送 transaction;而在失效的 500000 时间单位内,只有 sequence1 发送 transaction;当 sequence0 重新变有效后,sequence0sequence1 又开始交替发送 transaction。从某种程度上来说,is_relevantgrab 任务和 lock 任务是完全相反的。通过设置 is_relevant,可以使 sequence 主动放弃 sequencer 的使用权,而 grab 任务和 lock 任务则强占 sequencer 的所有权。

除了 is_relevant 外,sequence 中还有一个任务 wait_for_relevant 也与 sequence 的有效性相关:

清单 6-20

systemverilog 复制代码
文件:src/ch6/section6.2/6.2.4/wait_for_relevant/my_case0.sv 
  3 class sequence0 extends uvm_sequence #(my_transaction); 
... 
 14    virtual function bit is_relevant(); 
 15       if((num >= 3)&&(!has_delayed)) return 0; 
 16       else return 1; 
 17    endfunction 
 18 
 19    virtual task wait_for_relevant(); 
 20       #10000; 
 21       has_delayed = 1; 
 22    endtask 
 23 
 24    virtual task body(); 
... 
 27       repeat (10) begin 
 28          num++; 
 29          `uvm_do(m_trans) 
 30          `uvm_info("sequence0", "send one transaction", UVM_MEDIUM) 
 31       end 
... 
 35    endtask 
... 
 38 endclass 

当 sequencer 发现在其上启动的所有 sequence 都无效时,此时会调用 wait_for_relevant 并等待 sequence 变有效。当此 sequence 与代码清单 6-11 中的 sequence1 同时启动,并发送了 3 个 transaction 后,sequence0 变为无效。此后 sequencer 一直发送 sequence1 的 transaction,直到全部的 transaction 都发送完毕。此时,sequencer 发现 sequence0 无效,会调用其 wait_for_relevant。换言之,sequence0 失效是自己控制的,但是重新变得有效却是受其他 sequence 的控制。如果其他 sequence 永远不结束,那么 sequence0 将永远处于失效状态。这里与代码清单 6-19 中例子的区别是,代码清单 6-19 例子中 sequence0 并不是等待着 sequence1 的 transaction 全部发送完毕,而是自己主动控制着自己何时有效何时无效。

wait_for_relevant 中,必须将使 sequence 无效的条件清除。在代码清单 6-20 中,假如 wait_for_relevant 只是如下定义:

清单 6-21

systemverilog 复制代码
virtual task wait_for_relevant(); 
  #10000; 
endtask 

那么当 wait_for_relevant 返回后,sequencer 会继续调用 sequence0is_relevant,发现依然是无效状态,则继续调用 wait_for_relevant。系统会处于死循环的状态。

在代码清单 6-19 的例子中,通过控制延时(500000)单位时间来使 sequence0 重新变得有效。假如在这段时间内,sequence1 的 transaction 发送完毕后,而 sequence0 中又没有重载 wait_for_relevant 任务,那么将会给出如下错误提示:

text 复制代码
UVM_FATAL @ 1166700: uvm_test_top.env.i_agt.sqr@@seq0 [RELMSM] 
is_relevant()was implemented without defining wait_for_relevant() 

因此,is_relevantwait_for_relevant 一般应成对重载,不能只重载其中的一个。代码清单 6-19 的例子中没有重载 wait_for_relevant,是因为巧妙地设置了延时,可以保证不会调用到 wait_for_relevant。读者在使用时应该重载 wait_for_relevant 这个任务。


6.3 sequence相关宏及其实现

6.3.1 uvm_do系列宏

uvm_do 系列宏主要有以下 8 个:

清单 6-22

systemverilog 复制代码
来源:UVM 源代码 
`uvm_do(SEQ_OR_ITEM)
`uvm_do_pri(SEQ_OR_ITEM, PRIORITY)
`uvm_do_with(SEQ_OR_ITEM, CONSTRAINTS)
`uvm_do_pri_with(SEQ_OR_ITEM, PRIORITY, CONSTRAINTS)
`uvm_do_on(SEQ_OR_ITEM, SEQR)
`uvm_do_on_pri(SEQ_OR_ITEM, SEQR, PRIORITY)
`uvm_do_on_with(SEQ_OR_ITEM, SEQR, CONSTRAINTS)
`uvm_do_on_pri_with(SEQ_OR_ITEM, SEQR, PRIORITY, CONSTRAINTS)

其中 uvm_douvm_do_withuvm_do_priuvm_do_pri_with 在前面已经提到过了,这里只介绍另外 4 个。

uvm_do_on 用于显式地指定使用哪个 sequencer 发送此 transaction。它有两个参数,第一个是 transaction 的指针,第二个是 sequencer 的指针。当在 sequence 中使用 uvm_do 等宏时,其默认的 sequencer 就是此 sequence 启动时为其指定的 sequencer,sequence 将这个 sequencer 的指针放在其成员变量 m_sequencer 中。事实上,uvm_do 等价于:

清单 6-23

systemverilog 复制代码
`uvm_do_on(tr, this.m_sequencer) 

在这里看起来指定使用哪个 sequencer 似乎并没有用,它的真正作用要在 6.5 节 virtual sequence 中得到体现。

uvm_do_on_pri,它有三个参数,第一个参数是 transaction 的指针,第二个是 sequencer 的指针,第三个是优先级:

清单 6-24

systemverilog 复制代码
`uvm_do_on(tr, this, 100) 

uvm_do_on_with,它有三个参数,第一个参数是 transaction 的指针,第二个是 sequencer 的指针,第三个是约束:

清单 6-25

systemverilog 复制代码
`uvm_do_on_with(tr, this, {tr.pload.size == 100;}) 

uvm_do_on_pri_with,它有四个参数,是所有 uvm_do 宏中参数最多的一个。第一个参数是 transaction 的指针,第二个是 sequencer 的指针,第三个是优先级,第四个是约束:

清单 6-26

systemverilog 复制代码
`uvm_do_on_pri_with(tr, this, 100, {tr.pload.size == 100;}) 

uvm_do 系列的其他七个宏其实都是用 uvm_do_on_pri_with 宏来实现的。如 uvm_do 宏:

清单 6-27

systemverilog 复制代码
来源:UVM 源代码 
`define uvm_do(SEQ_OR_ITEM) \
  `uvm_do_on_pri_with(SEQ_OR_ITEM, m_sequencer, -1, {}) 

6.3.2 uvm_create与uvm_send

除了使用 uvm_do 宏产生 transaction,还可以使用 uvm_create 宏与 uvm_send 宏来产生:

清单 6-28

systemverilog 复制代码
文件:src/ch6/section6.3/6.3.2/my_case0.sv 
  3 class case0_sequence extends uvm_sequence #(my_transaction); 
... 
 10    virtual task body(); 
 11       int num = 0; 
 12       int p_sz; 
... 
 15       repeat (10) begin 
 16          num++; 
 17          `uvm_create(m_trans) 
 18          assert(m_trans.randomize()); 
 19          p_sz = m_trans.pload.size(); 
 20          {m_trans.pload[p_sz - 4], 
 21           m_trans.pload[p_sz - 3], 
 22           m_trans.pload[p_sz - 2], 
 23           m_trans.pload[p_sz - 1]} 
 24           = num; 
 25          `uvm_send(m_trans) 
 26       end 
... 
 30    endtask 
... 
 33 endclass 

uvm_create 宏的作用是实例化 transaction。当一个 transaction 被实例化后,可以对其做更多的处理,处理完毕后使用 uvm_send 宏发送出去。这种使用方式比 uvm_do 系列宏更加灵活。如在上例中,就将 pload 的最后 4 个 byte 替换为此 transaction 的序号。

事实上,在上述的代码中,也完全可以不使用 uvm_create 宏,而直接调用 new 进行实例化:

清单 6-29

systemverilog 复制代码
virtual task body(); 
... 
  m_trans = new("m_trans"); 
  assert(m_trans.randomize()); 
  p_sz = m_trans.pload.size(); 
  {m_trans.pload[p_sz - 4],  
   m_trans.pload[p_sz - 3],  
   m_trans.pload[p_sz - 2],  
   m_trans.pload[p_sz - 1]}  
   = num;  
  `uvm_send(m_trans) 
... 
endtask 

除了 uvm_send 外,还有 uvm_send_pri 宏,它的作用是在将 transaction 交给 sequencer 时设定优先级:

清单 6-30

systemverilog 复制代码
virtual task body(); 
... 
  m_trans = new("m_trans"); 
  assert(m_trans.randomize()); 
  p_sz = m_trans.pload.size(); 
  {m_trans.pload[p_sz - 4],  
   m_trans.pload[p_sz - 3],  
   m_trans.pload[p_sz - 2],  
   m_trans.pload[p_sz - 1]}  
   = num;  
  `uvm_send_pri(m_trans, 200) 
... 
endtask 

6.3.3 uvm_rand_send系列宏

uvm_rand_send 系列宏有如下几个:

清单 6-31

systemverilog 复制代码
来源:UVM 源代码 
`uvm_rand_send(SEQ_OR_ITEM)
`uvm_rand_send_pri(SEQ_OR_ITEM, PRIORITY)
`uvm_rand_send_with(SEQ_OR_ITEM, CONSTRAINTS)
`uvm_rand_send_pri_with(SEQ_OR_ITEM, PRIORITY, CONSTRAINTS)

uvm_rand_send 宏与 uvm_send 宏类似,唯一的区别是它会对 transaction 进行随机化。这个宏使用的前提是 transaction 已经被分配了空间,换言之,即已经实例化了:

清单 6-32

systemverilog 复制代码
m_trans = new("m_trans"); 
`uvm_rand_send(m_trans) 

uvm_rand_send_pri 宏用于指定 transaction 的优先级。它有两个参数,第一个是 transaction 的指针,第二个是优先级:

清单 6-33

systemverilog 复制代码
m_trans = new("m_trans"); 
`uvm_rand_send_pri(m_trans, 100) 

uvm_rand_send_with 宏,用于指定使用随机化时的约束,它有两个参数,第一个是 transaction 的指针,第二个是约束:

清单 6-34

systemverilog 复制代码
m_trans = new("m_trans"); 
`uvm_rand_send_with(m_trans, {m_trans.pload.size == 100;}) 

uvm_rand_send_pri_with 宏,用于指定优先级和约束,它有三个参数,第一个是 transaction 的指针,第二个是优先级,第三个是约束:

清单 6-35

systemverilog 复制代码
m_trans = new("m_trans"); 
`uvm_rand_send_pri_with(m_trans, 100, {m_trans.pload.size == 100;}) 

uvm_rand_send 系列宏及 uvm_send 系列宏的意义主要在于,如果一个 transaction 占用的内存比较大,那么很可能希望前后两次发送的 transaction 都使用同一块内存,只是其中的内容可以不同,这样比较节省内存。

6.3.4 start_item与finish_item

在前面的章节中一直使用宏来产生 transaction。宏隐藏了细节,方便了用户的使用,但是也给用户带来了困扰:宏到底做了什么事情?

不使用宏产生 transaction 的方式要依赖于两个任务:start_itemfinish_item。在使用这两个任务前,必须要先实例化 transaction 后才可以调用这两个任务:

清单 6-36

systemverilog 复制代码
tr = new("tr"); 
start_item(tr); 
finish_item(tr); 

完整使用如上两个任务构建的一个 sequence 如下:

清单 6-37

systemverilog 复制代码
virtual task body(); 
  repeat(10) begin 
    tr = new("tr"); 
    start_item(tr); 
    finish_item(tr); 
  end 
endtask 

上述代码中并没有对 tr 进行随机化。可以在 transaction 实例化后、finish_item 调用前对其进行随机化:

清单 6-38

systemverilog 复制代码
文件:src/ch6/section6.3/6.3.4/my_case0.sv 
  3 class case0_sequence extends uvm_sequence #(my_transaction); 
... 
  9    virtual task body(); 
... 
 13       repeat (10) begin 
 14          tr = new("tr"); 
 15          assert(tr.randomize() with {tr.pload.size == 200;}); 
 16          start_item(tr); 
 17          finish_item(tr); 
 18       end 
... 
 22    endtask 
... 
 25 endclass 

上述 assert 语句也可以放在 start_item 之后、finish_item 之前。uvm_do 系列宏其实是将下述动作封装在了一个宏中:

清单 6-39

systemverilog 复制代码
virtual task body(); 
... 
  tr = new("tr"); 
  start_item(tr); 
  assert(tr.randomize() with {tr.pload.size() == 200;}); 
  finish_item(tr); 
... 
endtask 

如果要指定 transaction 的优先级,那么要在调用 start_itemfinish_item 时都要加入优先级参数:

清单 6-40

systemverilog 复制代码
virtual task body(); 
... 
  start_item(tr, 100); 
  finish_item(tr, 100); 
... 
endtask 

如果不指定优先级参数,默认的优先级为 -1。

6.3.5 pre_do、mid_do与post_do

uvm_do 宏封装了从 transaction 实例化到发送的一系列操作,封装的越多,则其灵活性越差。为了增加 uvm_do 系列宏的功能,UVM 提供了三个接口:pre_domid_dopost_do

pre_do 是一个任务,在 start_item 中被调用,它是 start_item 返回前执行的最后一行代码,在它执行完毕后才对 transaction 进行随机化。mid_do 是一个函数,位于 finish_item 的最开始。在执行完此函数后,finish_item 才进行其他操作。post_do 也是一个函数,也位于 finish_item 中,它是 finish_item 返回前执行的最后一行代码。它们的执行顺序大致为:

清单 6-41

systemverilog 复制代码
sequencer.wait_for_grant(prior)  (task) \
start_item \
parent_seq.pre_do(1)             (task) /
\
                                                 \
`uvm_do* macros \
parent_seq.mid_do(item)          (func) \
/ \
sequencer.send_request(item)     (func)  \
finish_item / \
sequencer.wait_for_item_done()   (task)  /
parent_seq.post_do(item)         (func) /

wait_for_grantsend_requestwait_for_item_done 都是 UVM 内部的一些接口。

这三个接口函数/任务的使用示例如下:

清单 6-42

systemverilog 复制代码
文件:src/ch6/section6.3/6.3.5/my_case0.sv 
  3 class case0_sequence extends uvm_sequence #(my_transaction); 
  4    my_transaction m_trans; 
  5    int num; 
... 
 11    virtual task pre_do(bit is_item); 
 12       #100; 
 13       `uvm_info("sequence0", "this is pre_do", UVM_MEDIUM) 
 14    endtask 
 15 
 16    virtual function void mid_do(uvm_sequence_item this_item); 
 17       my_transaction tr; 
 18       int p_sz; 
 19       `uvm_info("sequence0", "this is mid_do", UVM_MEDIUM) 
 20       void'($cast(tr, this_item)); 
 21       p_sz = tr.pload.size(); 
 22       {tr.pload[p_sz - 4], 
 23        tr.pload[p_sz - 3], 
 24        tr.pload[p_sz - 2], 
 25        tr.pload[p_sz - 1]} = num; 
 26       tr.crc = tr.calc_crc(); 
 27       tr.print(); 
 28    endfunction 
 29 
 30    virtual function void post_do(uvm_sequence_item this_item); 
 31       `uvm_info("sequence0", "this is post_do", UVM_MEDIUM) 
 32    endfunction 
 33 
 34    virtual task body(); 
... 
 37       repeat (10) begin 
 38          num++; 
 39          `uvm_do(m_trans) 
 40       end 
... 
 44    endtask 
... 
 47 endclass 

pre_do 有一个参数,此参数用于表明 uvm_do 宏是在对一个 transaction 还是在对一个 sequence 进行操作,关于这一点请参考 6.4.1 节。mid_dopost_do 的两个参数是正在操作的 sequence 或者 item 的指针,但是其类型是 uvm_sequence_item 类型。通过 cast 可以转换成目标类型(示例中为 my_transaction)。


6.4 sequence进阶应用

6.4.1 嵌套的sequence

假设一个产生 CRC 错误包的 sequence 如下:

清单 6-43

systemverilog 复制代码
文件:src/ch6/section6.4/6.4.1/start/my_case0.sv 
  4 class crc_seq extends uvm_sequence#(my_transaction); 
... 
 10    virtual task body(); 
 11       my_transaction tr; 
 12       `uvm_do_with(tr, {tr.crc_err == 1; 
 13                      tr.dmac == 48'h980F;}) 
 14    endtask 
 15 endclass 

另外一个产生长包的 sequence 如下:

清单 6-44

systemverilog 复制代码
文件:src/ch6/section6.4/6.4.1/start/my_case0.sv 
 17 class long_seq extends uvm_sequence#(my_transaction); 
... 
 23   virtual task body(); 
 24     my_transaction tr; 
 25     `uvm_do_with(tr, {tr.crc_err == 0; 
 26                    tr.pload.size() == 1500; 
 27                    tr.dmac == 48'hF675;}) 
 28   endtask 
 29 endclass 

现在要写一个新的 sequence,它可以交替产生上面的两种包。那么在新的 sequence 里面可以这样写:

清单 6-45

systemverilog 复制代码
class case0_sequence extends uvm_sequence #(my_transaction); 
   virtual task body(); 
      my_transaction tr; 
      repeat (10) begin 
         `uvm_do_with(tr, {tr.crc_err == 1; 
                        tr.dmac == 48'h980F;}) 
         `uvm_do_with(tr, {tr.crc_err == 0; 
                        tr.pload.size() == 1500; 
                        tr.dmac == 48'hF675;}) 
      end 
   endtask 
endclass 

似乎这样写起来显得特别麻烦。产生的两种不同的包中,第一个约束条件有两个,第二个约束条件有三个。但是假如约束条件有十个呢?如果整个验证平台中有 30 个测试用例都用到这样的两种包,那就要在这 30 个测试用例的 sequence 中加入这些代码,这是一件相当恐怖的事情,而且特别容易出错。既然已经定义好 crc_seqlong_seq,那么有没有简单的方法呢?答案是肯定的。在一个 sequence 的 body 中,除了可以使用 uvm_do 宏产生 transaction 外,其实还可以启动其他的 sequence,即一个 sequence 内启动另外一个 sequence,这就是嵌套的 sequence:

清单 6-46

systemverilog 复制代码
文件:src/ch6/section6.4/6.4.1/start/my_case0.sv 
 31 class case0_sequence extends uvm_sequence #(my_transaction); 
... 
 37    virtual task body(); 
 38       crc_seq cseq; 
 39       long_seq lseq; 
... 
 42       repeat (10) begin 
 43          cseq = new("cseq"); 
 44          cseq.start(m_sequencer); 
 45          lseq = new("lseq"); 
 46          lseq.start(m_sequencer); 
 47       end 
... 
 51    endtask 
... 
 54 endclass 

直接在新的 sequence 的 body 中调用定义好的 sequence,从而实现 sequence 的重用。这个功能是非常强大的。在上面代码中,m_sequencercase0_sequence 在启动后所使用的 sequencer 的指针。但通常来说并不用这么麻烦,可以使用 uvm_do 宏来完成这些事情:

清单 6-47

systemverilog 复制代码
文件:src/ch6/section6.4/6.4.1/uvm_do/my_case0.sv 
 31 class case0_sequence extends uvm_sequence #(my_transaction); 
... 
 38    virtual task body(); 
 39       crc_seq cseq; 
 40       long_seq lseq; 
... 
 43       repeat (10) begin 
 44          `uvm_do(cseq) 
 45          `uvm_do(lseq) 
 46       end 
... 
 50    endtask 
... 
 53 endclass 

uvm_do 系列宏中,其第一个参数除了可以是 transaction 的指针外,还可以是某个 sequence 的指针。当第一个参数是 transaction 时,它如 6.3.4 节代码清单 6-39 中所示,调用 start_itemfinish_item;当第一个参数是 sequence 时,它调用此 sequence 的 start 任务。

除了 uvm_do 宏外,前面介绍的 uvm_send 宏、uvm_rand_send 宏、uvm_create 宏,其第一个参数都可以是 sequence 的指针。唯一例外的是 start_itemfinish_item,这两个任务的参数必须是 transaction 的指针。

6.4.2 在sequence中使用rand类型变量

在 transaction 的定义中,通常使用 rand 来对变量进行修饰,说明在调用 randomize 时要对此字段进行随机化。其实在 sequence 中也可以使用 rand 修饰符。有如下的 sequence,它有成员变量 ldmac

清单 6-48

systemverilog 复制代码
文件:src/ch6/section6.4/6.4.2/rand/my_case0.sv 
  4 class long_seq extends uvm_sequence#(my_transaction); 
  5    rand bit[47:0] ldmac; 
... 
 11    virtual task body(); 
 12       my_transaction tr; 
 13       `uvm_do_with(tr, {tr.crc_err == 0; 
 14                      tr.pload.size() == 1500; 
 15                      tr.dmac == ldmac;}) 
 16       tr.print(); 
 17    endtask 
 18 endclass 

这个 sequence 可以作为底层的 sequence 被顶层的 sequence 调用:

清单 6-49

systemverilog 复制代码
文件:src/ch6/section6.4/6.4.2/rand/my_case0.sv 
 20 class case0_sequence extends uvm_sequence #(my_transaction); 
... 
 27    virtual task body(); 
 28       long_seq lseq; 
... 
 31       repeat (10) begin 
 32          `uvm_do_with(lseq, {lseq.ldmac == 48'hFFFF;}) 
 33       end 
... 
 37    endtask 
... 
 40 endclass 

sequence 里可以添加任意多的 rand 修饰符,用以规范它产生的 transaction。sequence 与 transaction 都可以调用 randomize 进行随机化,都可以有 rand 修饰符的成员变量,从某种程度上来说,二者的界限比较模糊。这也就是为什么 uvm_do 系列宏可以接受 sequence 作为其参数的原因。

在 sequence 中定义 rand 类型变量时,要注意变量的命名。很多人习惯于变量的名字和 transaction 中相应字段的名字一致:

清单 6-50

systemverilog 复制代码
文件:src/ch6/section6.4/6.4.2/name/my_case0.sv 
  4 class long_seq extends uvm_sequence#(my_transaction); 
  5    rand bit[47:0] dmac; 
... 
 11    virtual task body(); 
 12      my_transaction tr; 
 13      `uvm_do_with(tr, {tr.crc_err == 0; 
 14                     tr.pload.size() == 1500; 
 15                     tr.dmac == dmac;}) 
 16       tr.print(); 
 17    endtask 
 18 endclass 

case0_sequence 中启动上述 sequence,并将 dmac 地址约束为 48'hFFFF,此时将会发现产生的 transaction 的 dmac 并不是 48'hFFFF,而是一个随机值!这是因为,当运行到上述代码的第 15 行时,编译器会首先去 my_transaction 寻找 dmac,如果找到了,就不再继续寻找。换言之,上述代码第 13 到第 15 行等价于:

清单 6-51

systemverilog 复制代码
`uvm_do_with(tr, {tr.crc_err == 0; 
               tr.pload.size() == 1500; 
               tr.dmac == tr.dmac;}) 

long_seq 中的 dmac 并没有起到作用。所以,在 sequence 中定义 rand 类型变量以向产生的 transaction 传递约束时,变量的名字一定要与 transaction 中相应字段的名字不同。

6.4.3 transaction类型的匹配

一个 sequencer 只能产生一种类型的 transaction,一个 sequence 如果要想在此 sequencer 上启动,那么其所产生的 transaction 的类型必须是这种 transaction 或者派生自这种 transaction。

如果一个 sequence 中产生的 transaction 的类型不是此种 transaction,那么将会报错:

清单 6-52

systemverilog 复制代码
class case0_sequence extends uvm_sequence #(my_transaction); 
   your_transaction y_trans; 
   virtual task body(); 
      repeat (10) begin 
         `uvm_do(y_trans) 
      end 
   endtask 
endclass 

嵌套 sequence 的前提是,在嵌套里面的所有 sequence 产生的 transaction 都可以被同一个 sequencer 所接受。

那么有没有办法将两个截然不同的 transaction 交给同一个 sequencer 呢?可以,只是需要将 sequencer 和 driver 能够接受的数据类型设置为 uvm_sequence_item

清单 6-53

systemverilog 复制代码
class my_sequencer extends uvm_sequencer #(uvm_sequence_item); 
class my_driver extends uvm_driver#(uvm_sequence_item); 

在 sequence 中可以交替发送 my_transactionyour_transaction

清单 6-54

systemverilog 复制代码
文件:src/ch6/section6.4/6.4.3/my_case0.sv 
 12 class case0_sequence extends uvm_sequence; 
 13    my_transaction m_trans; 
 14    your_transaction y_trans; 
... 
 20    virtual task body(); 
... 
 23       repeat (10) begin 
 24          `uvm_do(m_trans) 
 25          `uvm_do(y_trans) 
 26       end 
... 
 30    endtask 
 31 
 32    `uvm_object_utils(case0_sequence) 
 33 endclass 

这样带来的问题是,由于 driver 中接收的数据类型是 uvm_sequence_item,如果它要使用 my_transaction 或者 your_transaction 中的成员变量,必须使用 cast 转换:

清单 6-55

systemverilog 复制代码
文件:src/ch6/section6.4/6.4.3/my_driver.sv 
 24 task my_driver::main_phase(uvm_phase phase); 
 25    my_transaction m_tr; 
 26    your_transaction y_tr; 
... 
 31    while(1) begin 
 32       seq_item_port.get_next_item(req); 
 33       if($cast(m_tr, req)) begin 
 34          drive_my_transaction(m_tr); 
 35        `uvm_info("driver", "receive a transaction whose type is my_transaction", UVM_MEDIUM) 
 36       end 
 37       else if($cast(y_tr, req)) begin 
 38          drive_your_transaction(y_tr); 
 39       `uvm_info("driver", "receive a transaction whose type is your_transaction", UVM_MEDIUM) 
 40       end 
 41       else begin 
 42          `uvm_error("driver", "receive a transaction whose type is unknown") 
 43       end 
 44       seq_item_port.item_done(); 
 45    end 
 46 endtask 

6.4.4 p_sequencer的使用

考虑如下一种情况,在 sequencer 中存在如下成员变量:

清单 6-56

systemverilog 复制代码
文件:src/ch6/section6.4/6.4.4/my_sequencer.sv 
  4 class my_sequencer extends uvm_sequencer #(my_transaction); 
  5    bit[47:0] dmac; 
  6    bit[47:0] smac; 
... 
 12    virtual function void build_phase(uvm_phase phase); 
 13       super.build_phase(phase); 
 14       void'(uvm_config_db#(bit[47:0])::get(this, "", "dmac", dmac)); 
 15       void'(uvm_config_db#(bit[47:0])::get(this, "", "smac", smac)); 
 16    endfunction 
 17 
 18    `uvm_component_utils(my_sequencer) 
 19 endclass 

在其 build_phase 中,使用 config_db::get 得到这两个成员变量的值。之后 sequence 在发送 transaction 时,必须将目的地址设置为 dmac,源地址设置为 smac。现在的问题是,如何在 sequence 的 body 中得到这两个变量的值呢?

在 6.4.1 节中介绍嵌套的 sequence 时,引入了 m_sequencer 这个属于每个 sequence 的成员变量,但是如果直接使用 m_sequencer 得到这两个变量的值:

清单 6-57

systemverilog 复制代码
virtual task body(); 
... 
   repeat (10) begin 
      `uvm_do_with(m_trans, {m_trans.dmac == m_sequencer.dmac; 
                         m_trans.smac == m_sequencer.smac;}) 
   end 
... 
endtask 

如上写法会引起编译错误。其根源在于 m_sequenceruvm_sequencer_baseuvm_sequencer 的基类)类型的,而不是 my_sequencer 类型的。m_sequencer 的原型为:

清单 6-58

systemverilog 复制代码
来源:UVM 源代码 
protected uvm_sequencer_base m_sequencer; 

但是由于 case0_sequencemy_sequencer 上启动,其中的 m_sequencer 本质上是 my_sequencer 类型的,所以可以在 my_sequence 中通过 cast 转换将 m_sequencer 转换成 my_sequencer 类型,并引用其中的 dmacsmac

清单 6-59

systemverilog 复制代码
virtual task body(); 
   my_sequencer x_sequencer; 
... 
   $cast(x_sequencer, m_sequencer); 
   repeat (10) begin 
      `uvm_do_with(m_trans, {m_trans.dmac == x_sequencer.dmac; 
                         m_trans.smac == x_sequencer.smac;}) 
   end 
... 
endtask 

上述过程稍显麻烦。在实际的验证平台中,用到 sequencer 中成员变量的情况非常多。UVM 考虑到这种情况,内建了一个宏:uvm_declare_p_sequencer(SEQUENCER)。这个宏的本质是声明了一个 SEQUENCER 类型的成员变量,如在定义 sequence 时,使用此宏声明 sequencer 的类型:

清单 6-60

systemverilog 复制代码
文件:src/ch6/section6.4/6.4.4/my_case0.sv 
  3 class case0_sequence extends uvm_sequence #(my_transaction); 
  4   my_transaction m_trans; 
  5   `uvm_object_utils(case0_sequence) 
  6   `uvm_declare_p_sequencer(my_sequencer) 
... 
 24 endclass 

则相当于声明了如下的成员变量:

清单 6-61

systemverilog 复制代码
class case0_sequence extends uvm_sequence #(my_transaction); 
  my_sequencer p_sequencer; 
... 
endclass 

UVM 之后会自动将 m_sequencer 通过 cast 转换成 p_sequencer。这个过程在 pre_body() 之前就完成了。因此在 sequence 中可以直接使用成员变量 p_sequencer 来引用 dmacsmac

清单 6-62

systemverilog 复制代码
文件:src/ch6/section6.4/6.4.4/my_case0.sv 
  3 class case0_sequence extends uvm_sequence #(my_transaction); 
... 
 12    virtual task body(); 
... 
 15       repeat (10) begin 
 16          `uvm_do_with(m_trans, {m_trans.dmac == p_sequencer.dmac; 
 17                             m_trans.smac == p_sequencer.smac;}) 
 18       end 
... 
 22    endtask 
 23 
 24 endclass 

6.4.5 sequence的派生与继承

sequence 作为一个类,是可以从其中派生其他 sequence 的:

清单 6-63

systemverilog 复制代码
文件:src/ch6/section6.4/6.4.5/my_case0.sv 
  4 class base_sequence extends uvm_sequence #(my_transaction); 
  5   `uvm_object_utils(base_sequence) 
  6   `uvm_declare_p_sequencer(my_sequencer) 
  7   function  new(string name= "base_sequence"); 
  8      super.new(name); 
  9   endfunction 
 10   //define some common function and task 
 11 endclass 
 12 
 13 class case0_sequence extends base_sequence; 
... 
 31 endclass 

由于在同一个项目中各 sequence 都是类似的,所以可以将很多公用的函数或者任务写在 base sequence 中,其他 sequence 都从此 sequence 派生。

普通的 sequence 这样使用没有任何问题,但对于那些使用了 uvm_declare_p_sequencer 声明 p_sequencer 的 base sequence,在派生的 sequence 中是否也要调用此宏声明 p_sequencer?这个问题的答案是否定的,因为 uvm_declare_p_sequencer 的实质是在 base sequence 中声明了一个成员变量 p_sequencer。当其他的 sequence 从其派生时,p_sequencer 依然是新的 sequence 的成员变量,所以无须再声明一次了。

当然了,如果再声明一次,系统也并不会报错:

清单 6-64

systemverilog 复制代码
class base_sequence extends uvm_sequence #(my_transaction); 
  `uvm_object_utils(base_sequence) 
  `uvm_declare_p_sequencer(my_sequencer) 
... 
endclass 
class case0_sequence extends base_sequence; 
  `uvm_object_utils(case0_sequence) 
  `uvm_declare_p_sequencer(my_sequencer) 
... 
endclass 

虽然这相当于连续声明了两个成员变量 p_sequencer,但是由于这两个成员变量一个是属于父类的,一个是属于子类的,所以并不会出错。


6.5 virtual sequence的使用

6.5.1 带双路输入输出端口的DUT

在本书以前所有的例子中,使用的 DUT 几乎都是基于 2.2.1 节中所示的最简单的 DUT。为了说明 virtual sequence,本节引入附录 B 的代码 B-1 所示的 DUT。这个 DUT 相当于在 2.2.1 节所示的 DUT 的基础上增加了一组数据口,这组新的数据口与原先的数据口功能完全一样。新的数据端口增加后,由于这组新的数据端口与原先的一模一样,所以可以在 test 中再额外实例化一个 my_env

清单 6-65

systemverilog 复制代码
文件:src/ch6/section6.5/6.5.1/base_test.sv 
  4 class base_test extends uvm_test; 
  5 
  6   my_env         env0; 
  7   my_env         env1; 
... 
 16 endclass 
 17 
 18 
 19 function void base_test::build_phase(uvm_phase phase); 
 20   super.build_phase(phase); 
 21   env0  =  my_env::type_id::create("env0", this); 
 22   env1  =  my_env::type_id::create("env1", this); 
 23 endfunction 

top_tb 中做相应更改,多增加一组 my_if,并通过 config_db 将其设置为新的 env 中的 driver 和 monitor:

清单 6-66

systemverilog 复制代码
文件:src/ch6/section6.5/6.5.1/top_tb.sv 
 17 module top_tb; 
... 
 22 my_if input_if0(clk, rst_n); 
 23 my_if input_if1(clk, rst_n); 
 24 my_if output_if0(clk, rst_n); 
 25 my_if output_if1(clk, rst_n); 
 26 
 27 dut my_dut(.clk(clk), 
 28           .rst_n(rst_n), 
 29           .rxd0(input_if0.data), 
 30           .rx_dv0(input_if0.valid), 
 31           .rxd1(input_if1.data), 
 32           .rx_dv1(input_if1.valid), 
 33           .txd0(output_if0.data), 
 34           .tx_en0(output_if0.valid), 
 35           .txd1(output_if1.data), 
 36           .tx_en1(output_if1.valid)); 
... 
 55 initial begin 
 56   uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env0.i_agt.drv", "vif", input_if0); 
 57   uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env0.i_agt.mon","vif", input_if0); 
 58   uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env0.o_agt.mon", "vif", output_if0); 
 59   uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env1.i_agt.drv", "vif", input_if1); 
 60   uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env1.i_agt.mon", "vif", input_if1); 
 61   uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env1.o_agt.mon", "vif", output_if1); 
 62 end 
 63 
 64 endmodule 

通过在测试用例中设置两个 default_sequence,可以分别向两个数据端口施加激励:

清单 6-67

systemverilog 复制代码
文件:src/ch6/section6.5/6.5.1/my_case0.sv 
 36 function void my_case0::build_phase(uvm_phase phase); 
 37   super.build_phase(phase); 
 38 
 39   uvm_config_db#(uvm_object_wrapper)::set(this, 
 40                                    "env0.i_agt.sqr.main_phase", 
 41                                    "default_sequence", 
 42                                    case0_sequence::type_id::get()); 
 43   uvm_config_db#(uvm_object_wrapper)::set(this, 
 44                                    "env1.i_agt.sqr.main_phase", 
 45                                    "default_sequence", 
 46                                    case0_sequence::type_id::get()); 
 47 endfunction 

6.5.2 sequence之间的简单同步

在这个新的验证平台中有两个 driver,它们原本是完全等价的,但是出于某些原因的考虑,如 DUT 要求 driver0 必须先发送一个最大长度的包,在此基础上 driver1 才可以发送包。这是一个 sequence 之间同步的过程,一种很自然的想法是,将这个同步的过程使用一个全局的事件来完成:

清单 6-68

systemverilog 复制代码
文件:src/ch6/section6.5/6.5.2/my_case0.sv 
  3 event send_over;//global event 
  4 class drv0_seq extends uvm_sequence #(my_transaction); 
... 
 12   virtual task body(); 
... 
 15     `uvm_do_with(m_trans, {m_trans.pload.size == 1500;}) 
 16     ->send_over; 
 17     repeat (10) begin 
 18        `uvm_do(m_trans) 
 19        `uvm_info("drv0_seq", "send one transaction", UVM_MEDIUM) 
 20     end 
... 
 24   endtask 
 25 endclass 
 26 
 27 class drv1_seq extends uvm_sequence #(my_transaction); 
... 
 35   virtual task body(); 
... 
 38     @send_over; 
 39     repeat (10) begin 
 40        `uvm_do(m_trans) 
 41        `uvm_info("drv1_seq", "send one transaction", UVM_MEDIUM) 
 42     end 
... 
 46   endtask 
 47 endclass 

之后,通过 uvm_config_db 的方式分别将这两个 sequence 作为 env0.i_agt.sqrenv1.i_agt.sqrdefault_sequence

清单 6-69

systemverilog 复制代码
文件:src/ch6/section6.5/6.5.2/my_case0.sv 
 60 function void my_case0::build_phase(uvm_phase phase); 
 61   super.build_phase(phase); 
 62 
 63   uvm_config_db#(uvm_object_wrapper)::set(this, 
 64                                    "env0.i_agt.sqr.main_phase", 
 65                                    "default_sequence", 
 66                                    drv0_seq::type_id::get()); 
 67   uvm_config_db#(uvm_object_wrapper)::set(this, 
 68                                    "env1.i_agt.sqr.main_phase", 
 69                                    "default_sequence", 
 70                                    drv1_seq::type_id::get()); 
 71 endfunction 

当进入到 main_phase 时,这两个 sequence 会同步启动,但是由于 drv1_seq 要等待 send_over 事件的到来,所以它并不会马上产生 transaction,而 drv0_seq 则会直接产生 transaction。当 drv0_seq 发送完一个最长包后,send_over 事件被触发,于是 drv1_seq 开始产生 transaction。

6.5.3 sequence之间的复杂同步

上节中解决同步的方法看起来非常简单、实用。不过这里有两个问题,第一个问题是使用了一个全局的事件 send_over。全局变量对于初写代码的人来说是非常受欢迎的,但是几乎所有的老师及书本中都会这么说:除非有必要,否则尽量不要使用全局变量。使用全局变量的主要问题即它是全局可见的,本来只是打算在 drv0_seqdrv1_seq 中使用这个全局变量,但是假如其他的某个 sequence 也不小心使用了这个全局变量,在 drv0_seq 触发 send_over 事件之前,这个 sequence 已经触发了此事件,这是不允许的。所以应该尽量避免全局变量的使用。

第二个问题是上面只是实现了一次同步,如果是有多次同步怎么办?如 sequence A 要先执行,之后是 B,B 执行后才能是 C,C 执行后才能是 D,D 执行后才能是 E。这依然可以使用上面的全局方法解决,只是这会显得相当笨拙。

实现 sequence 之间同步的最好的方式就是使用 virtual sequence。从字面上理解,即虚拟的 sequence。虚拟的意思就是它根本就不发送 transaction,它只是控制其他的 sequence,起统一调度的作用。

如图 6-1 所示,为了使用 virtual sequence,一般需要有一个 virtual sequencer。virtual sequencer 里面包含指向其他真实 sequencer 的指针:

清单 6-70

systemverilog 复制代码
文件:src/ch6/section6.5/6.5.3/uvm_do_on/my_vsqr.sv 
  4 class my_vsqr extends uvm_sequencer; 
  5 
  6   my_sequencer p_sqr0; 
  7   my_sequencer p_sqr1; 
... 
 14 endclass 

图6-1(图例):virtual sequence 示意图------展示 virtual sequencer 中保存指向各真实 sequencer(p_sqr0、p_sqr1)的指针,并由 virtual sequence 统一调度、控制各真实 sequence 向对应 sequencer 发送 transaction 的结构关系。

base_test 中,实例化 vsqr,并将相应的 sequencer 赋值给 vsqr 中的 sequencer 的指针:

清单 6-71

systemverilog 复制代码
文件:src/ch6/section6.5/6.5.3/uvm_do_on/base_test.sv 
  4 class base_test extends uvm_test; 
  5 
  6   my_env         env0; 
  7   my_env         env1; 
  8   my_vsqr        v_sqr; 
... 
 18 endclass 
 19 
 20 
 21 function void base_test::build_phase(uvm_phase phase); 
 22   super.build_phase(phase); 
 23   env0  =  my_env::type_id::create("env0", this); 
 24   env1  =  my_env::type_id::create("env1", this); 
 25   v_sqr =  my_vsqr::type_id::create("v_sqr", this); 
 26 endfunction 
 27 
 28 function void base_test::connect_phase(uvm_phase phase); 
 29   v_sqr.p_sqr0 = env0.i_agt.sqr; 
 30   v_sqr.p_sqr1 = env1.i_agt.sqr; 
 31 endfunction 

在 virtual sequence 中则可以使用 uvm_do_on 系列宏来发送 transaction:

清单 6-72

systemverilog 复制代码
文件:src/ch6/section6.5/6.5.3/uvm_do_on/my_case0.sv 
 35 class case0_vseq extends uvm_sequence; 
 36    `uvm_object_utils(case0_vseq) 
 37    `uvm_declare_p_sequencer(my_vsqr) 
... 
 42    virtual task body(); 
 43       my_transaction tr; 
 44       drv0_seq seq0; 
 45       drv1_seq seq1; 
... 
 48       `uvm_do_on_with(tr, p_sequencer.p_sqr0, {tr.pload.size == 1500;}) 
 49       `uvm_info("vseq", "send one longest packet on p_sequencer.p_sqr0", UVM_MEDIUM) 
 50       fork 
 51          `uvm_do_on(seq0, p_sequencer.p_sqr0); 
 52          `uvm_do_on(seq1, p_sequencer.p_sqr1); 
 53       join 
... 
 57    endtask 
 58 endclass 

在 6.3.1 节介绍 uvm_do_on 宏时,读者对其用处感到非常迷茫,现在终于找到答案了。virtual sequence 是 uvm_do_on 宏用得最多的地方。

case0_vseq 中,先使用 uvm_do_on_withp_sequencer.sqr0 上发送一个最长包,当其发送完毕后,再启动 drv0_seqdrv1_seq。这里的 drv0_seqdrv1_seq 非常简单,两者之间不需要为同步做任何事情:

清单 6-73

systemverilog 复制代码
文件:src/ch6/section6.5/6.5.3/uvm_do_on/my_case0.sv 
  3 class drv0_seq extends uvm_sequence #(my_transaction); 
... 
 11    virtual task body(); 
 12       repeat (10) begin 
 13          `uvm_do(m_trans) 
 14          `uvm_info("drv0_seq", "send one transaction", UVM_MEDIUM) 
 15       end 
 16    endtask 
 17 endclass 
 18 
 19 class drv1_seq extends uvm_sequence #(my_transaction); 
... 
 27    virtual task body(); 
 28       repeat (10) begin 
 29          `uvm_do(m_trans) 
 30          `uvm_info("drv1_seq", "send one transaction", UVM_MEDIUM) 
 31       end 
 32    endtask 
 33 endclass 

在使用 uvm_do_on 宏的情况下,虽然 seq0 是在 case0_vseq 中启动,但是它最终会被交给 p_sequencer.p_sqr0,也即 env0.i_agt.sqr 而不是 v_sqr。这个就是 virtual sequence 和 virtual sequencer 中 virtual 的来源。它们各自并不产生 transaction,而只是控制其他的 sequence 为相应的 sequencer 产生 transaction。virtual sequence 和 virtual sequencer 只是起一个调度的作用。由于根本不直接产生 transaction,所以 virtual sequence 和 virtual sequencer 在定义时根本无需指明要发送的 transaction 数据类型。

如果不使用 uvm_do_on 宏,那么也可以手工启动 sequence,其效果完全一样。手工启动 sequence 的一个优势是可以向其中传递一些值:

清单 6-74

systemverilog 复制代码
文件:src/ch6/section6.5/6.5.3/start/my_case0.sv 
  3 class read_file_seq extends uvm_sequence #(my_transaction); 
  4   my_transaction m_trans; 
  5   string file_name; 
... 
 19 endclass 
... 
 37 class case0_vseq extends uvm_sequence; 
... 
 44   virtual task body(); 
 45     my_transaction tr; 
 46     read_file_seq seq0; 
 47     drv1_seq seq1; 
... 
 50     `uvm_do_on_with(tr, p_sequencer.p_sqr0, {tr.pload.size == 1500;}) 
 51     `uvm_info("vseq", "send one longest packet on p_sequencer.p_sqr0", UVM_MEDIUM) 
 52     seq0 = new("seq0"); 
 53     seq0.file_name = "data.txt"; 
 54     seq1 = new("seq1"); 
 55     fork 
 56        seq0.start(p_sequencer.p_sqr0); 
 57        seq1.start(p_sequencer.p_sqr1); 
 58     join 
... 
 62   endtask 
 63 endclass 

read_file_seq 中,需要一个字符串的文件名字,在手工启动时可以指定文件名字,但是 uvm_do 系列宏无法实现这个功能,因为 string 类型变量前不能使用 rand 修饰符。这就是手工启动 sequence 的优势。

case0_vseq 的定义中,一般都要使用 uvm_declare_p_sequencer 宏。这个在前文已经讲述过了,通过它可以引用 sequencer 的成员变量。

回顾一下,为了解决 sequence 的同步,之前使用 send_over 这个全局变量的方式来解决。那么在 virtual sequence 中是如何解决的呢?事实上这个问题在 virtual sequence 中根本就不是个问题。由于 virtual sequence 的 body 是顺序执行,所以只需要先产生一个最长的包,产生完毕后再将其他的 sequence 启动起来,没有必要去刻意地同步。这只是 virtual sequence 强大的调度功能的一个小小的体现。

virtual sequence 的使用可以减少 config_db 语句的使用。由于 config_db::set 函数的第二个路径参数是字符串,非常容易出错,所以减少 config_db 语句的使用可以降低出错的概率。在上节中,使用了两个 uvm_config_db 语句将两个 sequence 送给了相应的 sequencer 作为 default_sequence。假如验证平台中的 sequencer 有多个,如 10 个,那么就需要写 10 个 uvm_config_db 语句,这是一件很令人厌烦的事情。使用 virtual sequence 后可以将这 10 句只压缩成一句:

清单 6-75

systemverilog 复制代码
文件:src/ch6/section6.5/6.5.3/uvm_do_on/my_case0.sv 
 70 function void my_case0::build_phase(uvm_phase phase); 
... 
 73   uvm_config_db#(uvm_object_wrapper)::set(this, 
 74                                    "v_sqr.main_phase", 
 75                                    "default_sequence", 
 76                                    case0_vseq::type_id::get()); 
 77 endfunction 

virtual sequence 作为一种特殊的 sequence,也可以在其中启动其他的 virtual sequence:

清单 6-76

systemverilog 复制代码
文件:src/ch6/section6.5/6.5.3/multi_vseq/my_case0.sv 
 55 class case0_vseq extends uvm_sequence; 
... 
 62    virtual task body(); 
 63       cfg_vseq cvseq; 
... 
 66       `uvm_do(cvseq) 
... 
 70    endtask 
 71 endclass 

其中 cfg_vseq 是另外一个已经定义好的 virtual sequence。

6.5.4 仅在virtual sequence中控制objection

在 sequence 中可以使用 starting_phase 来控制验证平台的关闭。除了手工启动 sequence 时为 starting_phase 赋值外,只有将此 sequence 作为 sequencer 的某动态运行 phase 的 default_sequence 时,其 starting_phase 才不为 null。如果将某 sequence 作为 uvm_do 宏的参数,那么此 sequence 中的 starting_phase 是为 null 的。在此 sequence 中使用 starting_phase.raise_objection 是没有任何用处的:

清单 6-77

systemverilog 复制代码
文件:src/ch6/section6.5/6.5.3/my_case0.sv 
  3 class drv0_seq extends uvm_sequence #(my_transaction); 
... 
 11    virtual task body(); 
 12       if(starting_phase != null) begin 
 13          starting_phase.raise_objection(this); 
 14          `uvm_info("drv0_seq", "raise objection", UVM_MEDIUM) 
 15       end 
 16       else begin 
 17         `uvm_info("drv0_seq", "starting_phase is null, can't raise objection", UVM_MEDIUM) 
 18       end 
... 
 29    endtask 
 30 endclass 
 31 
 32 class case0_vseq extends uvm_sequence; 
... 
 39    virtual task body(); 
 40       drv0_seq seq0; 
 41       if(starting_phase != null) 
 42          starting_phase.raise_objection(this); 
 43       `uvm_do_on(seq0, p_sequencer.p_sqr0); 
 44       #100; 
 45       if(starting_phase != null) 
 46          starting_phase.drop_objection(this); 
 47    endtask 
 48 endclass 

运行上述代码,会发现 drv0_seq 中的 starting_phasenull,从而不会对 objection 进行操作。

若使 drv0_seq 中的 starting_phase 不为 null 其实比较容易解决,只要将父 sequence 的 starting_phase 赋值给子 sequence 的 starting_phase 即可。只是可惜 uvm_do 系列宏并不提供 starting_phase 的传递功能。5.2.3 节中提过要么在 scoreboard 中控制 objection,要么在 sequence 中控制。关于在 sequence 中控制 objection,在没有 virtual sequence 之前,这没有什么疑问。但是当 virtual sequence 存在时,尤其是 virtual sequence 中又可以启动其他的 virtual sequence 时,有三个地方可以控制 objection:一是普通的 sequence,二是中间层的 virtual sequence(如代码清单 6-76 中的 cfg_vseq),三是最顶层的 virtual sequence(代码清单 6-76 中的 case0_vseq)。那么应该在何处控制 objection 来最终控制验证平台的关闭呢?

一般来说,只在最顶层的 virtual sequence 中控制 objection。因为 virtual sequence 是起统一调度作用的,这种统一调度不只体现在 transaction 上,也应该体现在 objection 的控制上。在验证平台中使用 objection 时,经常会出现没有按照预期结束仿真的情况。这种情况下就需要层层地查找哪里有 objection 被提起了,哪里有 objection 被撤销了。虽然可以通过 5.2.5 节提及的 objection 调试手段来辅助进行,但它终归是一件比较麻烦的事情。如果大家约定俗成都只在最顶层的 virtual sequence 中控制 objection,那么在遇到这样的问题时,只查找最顶层的 virtual sequence 即可,从而大大提高效率。

6.5.5 在sequence中慎用fork join_none

将 6.5.1 节中的 DUT 的数据口扩展为 4 路,那么相应的验证平台中也要有 4 个完全相同的 driver、sequencer。那么 my_vsqr 可以这样定义:

清单 6-78

systemverilog 复制代码
文件:src/ch6/section6.5/6.5.5/my_vsqr.sv 
  4 class my_vsqr extends uvm_sequencer; 
  5 
  6   my_sequencer p_sqr[4]; 
... 
 12   `uvm_component_utils(my_vsqr) 
 13 endclass 

当 DUT 上电复位后,需要 4 个 my_driver 同时发送数据。在 virtual sequence 中可以使用 fork 来启动 4 个 sequence:

清单 6-79

systemverilog 复制代码
class case0_vseq extends uvm_sequence; 
   virtual task body(); 
      drv_seq dseq[4]; 
      for(int i = 0; i < 4; i++) 
         fork 
            automatic int j = i; 
            uvm_do_on(dseq[j], p_sequencer.p_sqr[j]); 
         join_none  
   endtask 
endclass 

这里使用了 join_none,由于 join_none 的特性,系统并不等 fork 起来的进程结束就进入下一次的 for 循环,因此上面的 for 循环的展开后如下:

清单 6-80

systemverilog 复制代码
class case0_vseq extends uvm_sequence; 
   virtual task body(); 
      drv_seq dseq[4]; 
      fork 
         uvm_do_on(dseq[0], p_sequencer.p_sqr[0]); 
      join_none  
      fork 
         uvm_do_on(dseq[1], p_sequencer.p_sqr[1]); 
      join_none 
      fork 
         uvm_do_on(dseq[2], p_sequencer.p_sqr[2]); 
      join_none 
      fork 
         uvm_do_on(dseq[3], p_sequencer.p_sqr[3]); 
      join_none 
    endtask 
endclass 

这样会有什么问题?

当 sequence 启动后会自动执行它的 body 任务。当 body 执行完成时,那么这个 sequence 就相当于已经完成了其使命,已经结束了。如果使用 fork join_none,由于 join_none 的特性,当使用 uvm_do_on 宏将四个 dseq 分别放在四个 p_sqr 上执行时,系统会新启动 4 个进程,但是并不等待这 4 个 mseq 执行完毕就直接返回了。返回之后就到了 endtask,此时系统认为这个 sequence 已经执行完成了。执行完成之后,系统将会清理这个 sequence 之前占据的内存空间,"杀死"掉由其启动的进程,于是这 4 个启动的 dseq 还没有完成就直接被"杀死"掉了。也就是说,看似分别往 4 个 p_sqr 分别丢了一个 sequence,但是事实上这个 sequence 根本没有执行。这是关键所在!

要避免这个问题有多种方法,一是使用 wait fork 语句:

清单 6-81

systemverilog 复制代码
文件:src/ch6/section6.5/6.5.5/my_case0.sv 
 19 class case0_vseq extends uvm_sequence; 
... 
 26    virtual task body(); 
 27       my_transaction tr; 
 28       drv_seq dseq[4]; 
... 
 31       for(int i = 0; i < 4; i++) 
 32          fork 
 33             automatic int j = i; 
 34             `uvm_do_on(dseq[j], p_sequencer.p_sqr[j]); 
 35          join_none 
 36       wait fork; 
... 
 40    endtask 
 41 endclass 

wait fork 语句将会等待前面被 fork 起来的进程执行完毕。

另外一种方法是使用 fork join

清单 6-82

systemverilog 复制代码
class case0_vseq extends uvm_sequence; 
   virtual task body(); 
      drv_seq dseq[4]; 
      fork 
         uvm_do_on(dseq[0], p_sequencer.p_sqr[0]); 
         uvm_do_on(dseq[1], p_sequencer.p_sqr[1]); 
         uvm_do_on(dseq[2], p_sequencer.p_sqr[2]); 
         uvm_do_on(dseq[3], p_sequencer.p_sqr[3]); 
      join 
   endtask 
endclass 

只是这样就无法使用 for 循环了。


6.6 在sequence中使用config_db

6.6.1 在sequence中获取参数

在 3.5 节中介绍 config_db 机制时,set 函数的目标都是一个 component,或者说,之前所有获取参数的操作都是在一个 component 中进行的。sequence 机制是 UVM 中最强大的机制之一,config_db 机制也对 sequence 机制提供了支持,可以在 sequence 中获取参数。

能够调用 config_db::get 的前提是已经进行了设置。sequence 本身是一个 uvm_object,它无法像 uvm_component 那样出现在 UVM 树中,从而很难确定在对其进行设置时的第二个路径参数。所以在 sequence 中使用 config_db::get 函数得到参数的最大障碍是路径问题。

在 UVM 中使用 get_full_name() 可以得到一个 component 的完整路径,同样的,此函数也可以在一个 sequence 中被调用,尝试着在一个 sequence 的 body 中调用此函数,并打印出返回值,其结果大体如下:

text 复制代码
uvm_test_top.env.i_agt.sqr.case0_sequence 

这个路径是由两个部分组成:此 sequence 的 sequencer 的路径,及实例化此 sequence 时传递的名字。因此,可以使用如下的方式为一个 sequence 传递参数:

清单 6-83

systemverilog 复制代码
文件:src/ch6/section6.6/6.6.1/my_case0.sv 
 43 function void my_case0::build_phase(uvm_phase phase); 
... 
 46   uvm_config_db#(int)::set(this, "env.i_agt.sqr.*", "count", 9); 
... 
 52 endfunction 

set 函数的第二个路径参数里面出现了通配符,这是因为 sequence 在实例化时名字一般是不固定的,而且有时是未知的(比如使用 default_sequence 启动的 sequence 的名字就是未知的),所以使用通配符。

在 sequence 中以如下的方式调用 config_db::get 函数:

清单 6-84

systemverilog 复制代码
文件:src/ch6/section6.6/6.6.1/my_case0.sv 
  3 class case0_sequence extends uvm_sequence #(my_transaction); 
... 
 11    virtual task pre_body(); 
 12       if(uvm_config_db#(int)::get(null, get_full_name(), "count", count)) 
 13          `uvm_info("seq0", $sformatf("get count value %0d via config_db", count), UVM_MEDIUM) 
 14       else 
 15          `uvm_error("seq0", "can't get count value!") 
 16    endtask 
... 
 30 endclass 

这里需要引起关注的是第一个参数。在 get 函数原型中,第一个参数必须是一个 component,而 sequence 不是一个 component,所以这里不能使用 this 指针,只能使用 null 或者 uvm_root::get()。前文已经提过,当使用 null 时,UVM 会自动将其替换为 uvm_root::get(),再加上第二个参数 get_full_name(),就可以完整地得到此 sequence 的路径,从而得到参数。

6.6.2 在sequence中设置参数

与获取参数相比,在 sequence 中使用 config_db::set 设置参数就比较简单。有了在 top_tb 中设置 virtual interface 的经验,读者在这里可以使用类似的方式为 UVM 树中的任意结点传递参数:

清单 6-85

systemverilog 复制代码
文件:src/ch6/section6.6/6.6.2/component/my_case0.sv 
 33 class case0_vseq extends uvm_sequence; 
... 
 40    virtual task body(); 
... 
 46       fork 
 47          `uvm_do_on(seq0, p_sequencer.p_sqr0); 
 48          `uvm_do_on(seq1, p_sequencer.p_sqr1); 
 49          begin 
 50             #10000; 
 51                uvm_config_db#(bit)::set(uvm_root::get(), "uvm_test_top.env0.scb", "cmp_en", 0); 
 52             #10000; 
 53            uvm_config_db#(bit)::set(uvm_root::get(), "uvm_test_top.env0.scb", "cmp_en", 1); 
 54          end 
 55       join 
... 
 59    endtask 
 60 endclass 

上例中是向 scoreboard 中传递了一个 cmp_en 的参数。除了向 component 中传递参数外,也可以向 sequence 中传递参数:

清单 6-86

systemverilog 复制代码
文件:src/ch6/section6.6/6.6.2/sequence/my_case0.sv 
  3 class drv0_seq extends uvm_sequence #(my_transaction); 
  4   my_transaction m_trans; 
  5   bit first_start; 
  6   `uvm_object_utils(drv0_seq) 
  7 
  8   function  new(string name= "drv0_seq"); 
  9     super.new(name); 
 10     first_start = 1; 
 11   endfunction 
 12 
 13   virtual task body(); 
 14    void'(uvm_config_db#(bit)::get(uvm_root::get(), get_full_name(), "first_start", first_start)); 
 15     if(first_start) 
 16       `uvm_info("drv0_seq", "this is the first start of the sequence", UVM_MEDIUM) 
 17     else 
 18        `uvm_info("drv0_seq", "this is not the first start of the sequence",UVM_MEDIUM) 
 19     uvm_config_db#(bit)::set(uvm_root::get(), "uvm_test_top.v_sqr.*", "first_start", 0); 
... 
 23   endtask 
 24 endclass 

这个 sequence 向自己传递了一个参数:first_start。在一次仿真中,当此 sequence 第一次启动时,其 first_start 值为 1;当后面再次启动时,其 first_start 为 0。根据 first_start 值的不同,可以在 body 中有不同的行为。

这里需要注意的是,由于此 sequence 在 virtual sequence 中被启动,所以其 get_full_name 的结果应该是 uvm_test_top.v_sqr.*,而不是 uvm_test_top.env0.i_agt.sqr.*,所以在设置时,第二个参数应该是前者。

6.6.3 wait_modified的使用

在上一节的例子中,向 scoreboard 传递了一个 cmp_en 的参数,scoreboard 可以根据此参数决定是否对收到的 transaction 进行检查。在做一些异常用例测试的时候,经常用到这种方式。但是关键是如何在 scoreboard 中获取这个参数。

在前面的章节中,scoreboard 都是在 build_phase 中调用 get 函数,并且调用的前提是参数已经被设置过。一个 sequence 是在 task phase 中运行的,当其设置一个参数的时候,其时间往往是不固定的。

针对这种不固定的设置参数的方式,UVM 中提供了 wait_modified 任务,它的参数有三个,与 config_db::get 的前三个参数完全一样。当它检测到第三个参数的值被更新过后,它就返回,否则一直等待在那里。其调用方式如下:

清单 6-87

systemverilog 复制代码
文件:src/ch6/section6.6/6.6.3/component/my_scoreboard.sv 
 24 task my_scoreboard::main_phase(uvm_phase phase); 
... 
 30   fork 
 31      while(1) begin 
 32         uvm_config_db#(bit)::wait_modified(this, "", "cmp_en"); 
 33         void'(uvm_config_db#(bit)::get(this, "", "cmp_en", cmp_en)); 
 34        `uvm_info("my_scoreboard", $sformatf("cmp_en value modified, the new value is %0d", cmp_en), UVM_LOW) 
 35      end 
... 
 62   join 
 63 endtask 

在上述代码中,wait_modifiedmain_phase 中的其他进程在同一时刻被 fork 起来,当检测到参数值被设置后,立刻调用 config_db::get 得到新的参数。其他进程可以根据新的参数值决定后续的比对策略。

get 函数一样,除了可以在一个 component 中使用外,还可以在一个 sequence 中调用 wait_modified 任务:

清单 6-88

systemverilog 复制代码
文件:src/ch6/section6.6/6.6.3/sequence/my_case0.sv 
  3 class drv0_seq extends uvm_sequence #(my_transaction); 
... 
 11    virtual task body(); 
 12       bit send_en = 1; 
 13       fork 
 14          while(1) begin 
 15             uvm_config_db#(bit)::wait_modified(null, get_full_name(), "send_en"); 
 16             void'(uvm_config_db#(bit)::get(null, get_full_name, "send_en", send_en)); 
 17             `uvm_info("drv0_seq", $sformatf("send_en value modified, the new value is %0d", send_en), UVM_LOW) 
 18          end 
 19       join_none 
... 
 23    endtask 
 24 endclass 

6.7 response的使用

6.7.1 put_response与get_response

sequence 机制提供了一种 sequence → sequencer → driver 的单向数据传输机制。但是在复杂的验证平台中,sequence 需要根据 driver 对 transaction 的反应来决定接下来要发送的 transaction,换言之,sequence 需要得到 driver 的一个反馈。sequence 机制提供对这种反馈的支持,它允许 driver 将一个 response 返回给 sequence。

如果需要使用 response,那么在 sequence 中需要使用 get_response 任务:

清单 6-89

systemverilog 复制代码
文件:src/ch6/section6.7/6.7.1/my_case0.sv 
  3 class case0_sequence extends uvm_sequence #(my_transaction); 
... 
 10    virtual task body(); 
... 
 13       repeat (10) begin 
 14          `uvm_do(m_trans) 
 15          get_response(rsp); 
 16          `uvm_info("seq", "get one response", UVM_MEDIUM) 
 17          rsp.print(); 
 18       end 
... 
 22    endtask 
 23 
 24    `uvm_object_utils(case0_sequence) 
 25 endclass 

在 driver 中,则需要使用 put_response 任务:

清单 6-90

systemverilog 复制代码
文件:src/ch6/section6.7/6.7.1/my_driver.sv 
 22 task my_driver::main_phase(uvm_phase phase); 
... 
 27    while(1) begin 
 28       seq_item_port.get_next_item(req); 
 29       drive_one_pkt(req); 
 30       rsp = new("rsp"); 
 31       rsp.set_id_info(req); 
 32       seq_item_port.put_response(rsp); 
 33       seq_item_port.item_done(); 
 34    end 
 35 endtask 

这里的关键是设置 set_id_info 函数,它将 req 的 id 等信息复制到 rsp 中。由于可能存在多个 sequence 在同一个 sequencer 上启动的情况,只有设置了 rsp 的 id 等信息,sequencer 才知道将 response 返回给哪个 sequence。

除了使用 put_response 外,UVM 还支持直接将 response 作为 item_done 的参数:

清单 6-91

systemverilog 复制代码
while(1) begin 
   seq_item_port.get_next_item(req); 
   drive_one_pkt(req); 
   rsp = new("rsp"); 
   rsp.set_id_info(req); 
   seq_item_port.item_done(rsp); 
end 

6.7.2 response的数量问题

通常来说,一个 transaction 对应一个 response,但是事实上,UVM 也支持一个 transaction 对应多个 response 的情况,在这种情况下,在 sequence 中需要多次调用 get_response,而在 driver 中,需要多次调用 put_response

清单 6-92

systemverilog 复制代码
task my_driver::main_phase(uvm_phase phase); 
   while(1) begin 
      seq_item_port.get_next_item(req); 
      drive_one_pkt(req); 
      rsp = new("rsp"); 
      rsp.set_id_info(req); 
      seq_item_port.put_response(rsp); 
      seq_item_port.put_response(rsp); 
      seq_item_port.item_done(); 
   end 
endtask 

class case0_sequence extends uvm_sequence #(my_transaction); 
   virtual task body(); 
      repeat (10) begin 
         `uvm_do(m_trans) 
         get_response(rsp); 
         rsp.print(); 
         get_response(rsp); 
         rsp.print(); 
      end 
   endtask 
endclass 

当存在多个 response 时,将 response 作为 item_done 参数的方式就不适用了。由于一个 transaction 只能对应一个 item_done,所以使用多次 item_done(rsp) 是会出错的。

response 机制的原理是 driver 将 rsp 推送给 sequencer,而 sequencer 内部维持一个队列,当有新的 response 进入时,就推入此队列。但是此队列的大小并不是无限制的,在默认情况下,其大小为 8。当队列中有 8 个 response 时,如果 driver 再次向此队列推送新的 response,UVM 就会给出如下错误提示:

text 复制代码
UVM_ERROR @ 1753500000: uvm_test_top.env.i_agt.sqr@@case0_sequence [uvm_test_top.env.i_agt.sqr.case0_sequence] 
Response queue overflow, response was dropped 

因此,如果在 driver 中每个 transaction 后都发送一个 response,而 sequence 又没能及时 get_response,sequencer 中的 response 队列就存在溢出的风险。

6.7.3 response handler与另类的response

前面讲述的 get_responseput_response 是一一对应的。当在 sequence 中启动 get_response 时,进程就会阻塞在那里,一直到 response_queue 中被放入新的记录。如果 driver 能够马上将 response 通过 put_response 的方式传回 sequence,那么 sequence 被阻塞的进程就会得到释放,可以接着发送下一个 transaction 给 driver。但是假如 driver 需要延时较长的一段时间才能将 transaction 传回,在此期间,driver 希望能够继续从 sequence 得到新的 transaction 并驱动它,但是由于 sequence 被阻塞在了那里,根本不可能发出新的 transaction。

发生上述情况的主要原因为 sequence 中发送 transaction 与 get_response 是在同一个进程中执行的,假如将二者分离开来,在不同的进程中运行将会得到不同的结果。在这种情况下需要使用 response_handler

清单 6-93

systemverilog 复制代码
文件:src/ch6/section6.7/6.7.3/rsp_handler/my_case0.sv 
  3 class case0_sequence extends uvm_sequence #(my_transaction); 
... 
 10    virtual task pre_body(); 
 11       use_response_handler(1); 
 12    endtask 
 13 
 14    virtual function void response_handler(uvm_sequence_item response); 
 15       if(!$cast(rsp, response)) 
 16          `uvm_error("seq", "can't cast") 
 17       else begin 
 18          `uvm_info("seq", "get one response", UVM_MEDIUM) 
 19          rsp.print(); 
 20       end 
 21    endfunction 
 22 
 23    virtual task body(); 
 24       if(starting_phase != null) 
 25          starting_phase.raise_objection(this); 
 26       repeat (10) begin 
 27          `uvm_do(m_trans) 
 28       end 
 29       #100; 
 30       if(starting_phase != null) 
 31          starting_phase.drop_objection(this); 
 32    endtask 
 33 
 34    `uvm_object_utils(case0_sequence) 
 35 endclass 

由于 response handler 功能默认是关闭的,所以要使用 response_handler,首先需要调用 use_response_handler 函数,打开 sequence 的 response handler 功能。

当打开 response handler 功能后,用户需要重载虚函数 response_handler。此函数的参数是一个 uvm_sequence_item 类型的指针,需要首先将其通过 cast 转换变成 my_transaction 类型,之后就可以根据 rsp 的值来决定后续 sequence 的行为。

无论是 put/get_response 或者 response_handler,都是新建了一个 transaction,并将其返回给 sequence。事实上,当一个 uvm_do 语句执行完毕后,其第一个参数并不是一个空指针,而是指向刚刚被送给 driver 的 transaction。利用这一点,可以实现一种另类的 response:

清单 6-94

systemverilog 复制代码
文件:src/ch6/section6.7/6.7.3/smart/my_driver.sv 
 22 task my_driver::main_phase(uvm_phase phase); 
... 
 27    while(1) begin 
 28       seq_item_port.get_next_item(req); 
 29       drive_one_pkt(req); 
 30       req.frm_drv = "this is information from driver"; 
 31       seq_item_port.item_done(); 
 32    end 
 33 endtask 

driver 中向 req 中的成员变量赋值,而 sequence 则检测这个值:

清单 6-95

systemverilog 复制代码
文件:src/ch6/section6.7/6.7.3/smart/my_case0.sv 
  3 class case0_sequence extends uvm_sequence #(my_transaction); 
... 
 10    virtual task body(); 
... 
 13       repeat (10) begin 
 14          `uvm_do(m_trans) 
 15         `uvm_info("seq", $sformatf("get information from driver: %0s", m_trans.frm_drv), UVM_MEDIUM) 
 16       end 
... 
 20    endtask 
 21 
 22    `uvm_object_utils(case0_sequence) 
 23 endclass 

这种另类的 response 在很多总线的 driver 中用到。读者可以参考 7.1.1 节的内容。

6.7.4 rsp与req类型不同

前面所有的例子中,response 的类型都与 req 的类型完全相同。UVM 也支持 response 与 req 类型不同的情况。

uvm_driveruvm_sequenceruvm_sequence 的原型分别是:

清单 6-96

systemverilog 复制代码
来源:UVM 源代码 
class uvm_driver #(type REQ=uvm_sequence_item, 
                 type RSP=REQ) extends uvm_component; 
class uvm_sequencer #(type REQ=uvm_sequence_item, 
                      RSP=REQ) 
                              extends uvm_sequencer_param_base #(REQ, RSP); 
virtual class uvm_sequence #(type REQ = uvm_sequence_item, 
                           type RSP = REQ) 
extends uvm_sequence_base; 

在前面章节的例子中只向它们传递了一个参数,因此 response 与 req 的类型是一样的。如果要使用不同类型的 rsp 与 req,那么 driver、sequencer 与 sequence 在定义时都要传入两个参数:

清单 6-97

systemverilog 复制代码
class my_driver extends uvm_driver#(my_transaction, your_transaction); 
class my_sequencer extends uvm_sequencer #(my_transaction, your_transaction); 
class case0_sequence extends uvm_sequence #(my_transaction, your_transaction); 

之后,可以使用 put_response 来发送 response:

清单 6-98

systemverilog 复制代码
文件:src/ch6/section6.7/6.7.4/my_driver.sv 
 22 task my_driver::main_phase(uvm_phase phase); 
... 
 27    while(1) begin 
 28       seq_item_port.get_next_item(req); 
 29       drive_one_pkt(req); 
 30       rsp = new("rsp"); 
 31       rsp.set_id_info(req); 
 32       rsp.information = "driver information"; 
 33       seq_item_port.put_response(rsp); 
 34       seq_item_port.item_done(); 
 35    end 
 36 endtask 

使用 get_response 来接收 response:

清单 6-99

systemverilog 复制代码
文件:src/ch6/section6.7/6.7.4/my_case0.sv 
  3 class case0_sequence extends uvm_sequence #(my_transaction, your_transaction); 
... 
 10    virtual task body(); 
... 
 13       repeat (10) begin 
 14          `uvm_do(m_trans) 
 15          get_response(rsp); 
 16       `uvm_info("seq", $sformatf("response information is: %0s", rsp.information), UVM_MEDIUM) 
 17       end 
... 
 21    endtask 
 22 
 23    `uvm_object_utils(case0_sequence) 
 24 endclass 

除了 put/get_response 外,也可以使用 response handler,这与 req 及 rsp 类型相同时完全一样。


6.8 sequence library

6.8.1 随机选择sequence

所谓 sequence library,就是一系列 sequence 的集合。sequence_library 类的原型为:

清单 6-100

systemverilog 复制代码
来源:UVM 源代码 
class uvm_sequence_library #(type REQ=uvm_sequence_item,RSP=REQ) extends uvm_sequence #(REQ,RSP); 

由上述代码可以看出 sequence library 派生自 uvm_sequence,从本质上说它是一个 sequence。它根据特定的算法随机选择注册在其中的一些 sequence,并在 body 中执行这些 sequence。

一个 sequence library 的定义如下:

清单 6-101

systemverilog 复制代码
文件:src/ch6/section6.8/6.8.1/my_case0.sv 
  4 class simple_seq_library extends uvm_sequence_library#(my_transaction); 
  5    function  new(string name= "simple_seq_library"); 
  6       super.new(name); 
  7       init_sequence_library(); 
  8    endfunction 
  9 
 10    `uvm_object_utils(simple_seq_library) 
 11    `uvm_sequence_library_utils(simple_seq_library)
 12 
 13 endclass 

在定义 sequence library 时有三点要特别注意:一是从 uvm_sequence 派生时要指明此 sequence library 所产生的 transaction 类型,这点与普通的 sequence 相同;二是在其 new 函数中要调用 init_sequence_library,否则其内部的候选 sequence 队列就是空的;三是要调用 uvm_sequence_library_utils 注册。

一个 sequence library 在定义之后,如果没有其他的 sequence 注册到其中,是没有任何意义的。一个 sequence 在定义时使用宏 uvm_add_to_seq_lib 来将其加入某个 sequence library 中:

清单 6-102

systemverilog 复制代码
文件:src/ch6/section6.8/6.8.1/my_case0.sv 
 15 class seq0 extends uvm_sequence#(my_transaction); 
... 
 20    `uvm_object_utils(seq0) 
 21    `uvm_add_to_seq_lib(seq0, simple_seq_library) 
 22    virtual task body(); 
 23       repeat(10) begin 
 24          `uvm_do(req) 
 25          `uvm_info("seq0", "this is seq0", UVM_MEDIUM) 
 26       end 
 27    endtask 
 28 endclass 

uvm_add_to_seq_lib 有两个参数,第一个是此 sequence 的名字,第二个是要加入的 sequence library 的名字。一个 sequence 可以加入多个不同的 sequence library 中:

清单 6-103

systemverilog 复制代码
class seq0 extends uvm_sequence#(my_transaction); 
   `uvm_object_utils(seq0) 
   `uvm_add_to_seq_lib(seq0, simple_seq_library) 
   `uvm_add_to_seq_lib(seq0, hard_seq_library) 
   virtual task body(); 
      repeat(10) begin 
         `uvm_do(req) 
         `uvm_info("seq0", "this is seq0", UVM_MEDIUM) 
      end 
   endtask  
endclass 

同样的,可以有多个 sequence 加入同一 sequence library 中:

清单 6-104

systemverilog 复制代码
文件:src/ch6/section6.8/6.8.1/my_case0.sv 
 30 class seq1 extends uvm_sequence#(my_transaction); 
... 
 35    `uvm_object_utils(seq1) 
 36    `uvm_add_to_seq_lib(seq1, simple_seq_library) 
 37    virtual task body(); 
 38       repeat(10) begin 
 39          `uvm_do(req) 
 40          `uvm_info("seq1", "this is seq1", UVM_MEDIUM) 
 41       end 
 42    endtask 
 43 endclass 

当 sequence 与 sequence library 定义好后,可以将 sequence library 作为 sequencer 的 default_sequence

清单 6-105

systemverilog 复制代码
文件:src/ch6/section6.8/6.8.1/my_case0.sv 
 85 function void my_case0::build_phase(uvm_phase phase); 
 86    super.build_phase(phase); 
 87 
 88    uvm_config_db#(uvm_object_wrapper)::set(this, 
 89                                    "env.i_agt.sqr.main_phase", 
 90                                    "default_sequence", 
 91                                    simple_seq_library::type_id::get()); 
 92 endfunction 

执行上述代码,将发现 UVM 会随机从加入 simple_seq_library 的 sequence 中选择几个,并顺序启动它们。

6.8.2 控制选择算法

在上节中,sequence library 随机从其 sequence 队列中选择几个执行。这是由其变量 selection_mode 决定的,这个变量的定义为:

清单 6-106

systemverilog 复制代码
来源:UVM 源代码 
uvm_sequence_lib_mode selection_mode; 

uvm_sequence_lib_mode 是一个枚举类型,共有四个值:

清单 6-107

systemverilog 复制代码
来源:UVM 源代码 
typedef enum 
{ 
  UVM_SEQ_LIB_RAND, 
  UVM_SEQ_LIB_RANDC, 
  UVM_SEQ_LIB_ITEM, 
  UVM_SEQ_LIB_USER 
} uvm_sequence_lib_mode; 

UVM_SEQ_LIB_RAND 就是完全的随机,上节中的例子使用的就是这种算法。

UVM_SEQ_LIB_RANDC 就是将加入其中的 sequence 随机排一个顺序,然后按照此顺序执行。这可以保证每个 sequence 执行一遍,在所有的 sequence 被执行完一遍之前,不会有 sequence 被执行第二次,其配置方式如下:

清单 6-108

systemverilog 复制代码
文件:src/ch6/section6.8/6.8.2/randc/my_case0.sv 
 85 function void my_case0::build_phase(uvm_phase phase); 
... 
 92   uvm_config_db#(uvm_sequence_lib_mode)::set(this, 
 93                                    "env.i_agt.sqr.main_phase", 
 94                                    "default_sequence.selection_mode", 
 95                                    UVM_SEQ_LIB_RANDC); 
 96 endfunction 

UVM_SEQ_LIB_ITEM 的意思是 sequence library 并不执行其 sequence 队列中的 sequence,而是自己产生 transaction。换言之,sequence library 在此种情况下就是一个普通的 sequence,只是其产生的 transaction 除了定义时施加的约束外,没有任何额外的约束。

UVM_SEQ_LIB_USER 是用户自定义选择的算法。此时需要用户重载 select_sequence 参数:

清单 6-109

systemverilog 复制代码
文件:src/ch6/section6.8/6.8.2/user/my_case0.sv 
  4 class simple_seq_library extends uvm_sequence_library#(my_transaction); 
  5    function  new(string name= "simple_seq_library"); 
  6       super.new(name); 
  7       init_sequence_library(); 
  8    endfunction 
  9 
 10    `uvm_object_utils(simple_seq_library) 
 11    `uvm_sequence_library_utils(simple_seq_library)
 12 
 13    virtual function int unsigned select_sequence(int unsigned max); 
 14       static int unsigned index[$]; 
 15       static bit inited; 
 16       int value; 
 17       if(!inited) begin 
 18          for(int i = 0; i <= max; i++) begin 
 19             if((sequences[i].get_type_name() == "seq0") || 
 20                (sequences[i].get_type_name() == "seq1") || 
 21                (sequences[i].get_type_name() == "seq3")) 
 22                index.push_back(i); 
 23          end 
 24          inited = 1; 
 25       end 
 26       value = $urandom_range(0, index.size() - 1); 
 27       return index[value]; 
 28    endfunction 
 29 endclass 

假设有 4 个 sequence 加入了 sequence library 中:seq0seq1seq2seq3。现在由于各种原因,不想使用 seq2 了。上述代码的 select_sequence 第一次被调用时初始化 index 队列,把 seq0seq1seq3sequences 中的索引号存入其中。之后,从 index 中随机选择一个值返回,相当于是从 seq0seq1seq3 随机选一个执行。sequences 是 sequence library 中存放候选 sequence 的队列。select_sequence 会传入一个参数 maxselect_sequence 函数必须返回一个介于 0 到 max 之间的数值。如果 sequences 队列的大小为 4,那么传入的 max 的数值是 3,而不是 4。

6.8.3 控制执行次数

在 6.8.1 节及 6.8.2 节中,执行的次数都是 10 次,这是由 sequence library 内部的两个变量控制的:

清单 6-110

systemverilog 复制代码
来源:UVM 源代码 
int unsigned min_random_count=10; 
int unsigned max_random_count=10; 

sequence library 会在 min_random_countmax_random_count 之间随意选择一个数来作为执行次数。这里只能选择 10。当 selection_modeUVM_SEQ_LIB_ITEM 时,将会产生 10 个 item;为其他模式时,将会顺序启动 10 个 sequence。可以设置这两个值为其他值来改变迭代次数:

清单 6-111

systemverilog 复制代码
文件:src/ch6/section6.8/6.8.3/my_case0.sv 
 85 function void my_case0::build_phase(uvm_phase phase); 
... 
 88   uvm_config_db#(uvm_object_wrapper)::set(this, 
 89                                    "env.i_agt.sqr.main_phase", 
 90                                    "default_sequence", 
 91                                    simple_seq_library::type_id::get()); 
 92   uvm_config_db#(uvm_sequence_lib_mode)::set(this, 
 93                                    "env.i_agt.sqr.main_phase", 
 94                                    "default_sequence.selection_mode", 
 95                                    UVM_SEQ_LIB_ITEM); 
 96   uvm_config_db#(int unsigned)::set(this, 
 97                                    "env.i_agt.sqr.main_phase", 
 98                                    "default_sequence.min_random_count", 
 99                                    5); 
100   uvm_config_db#(int unsigned)::set(this, 
101                                    "env.i_agt.sqr.main_phase", 
102                                    "default_sequence.max_random_count", 
103                                   20); 
104 endfunction 

上述设置将会产生最多 20 个,最少 5 个 transaction。

6.8.4 使用sequence_library_cfg

在代码清单 6-111 中使用 3 个 config_db 设置迭代次数和选择算法稍显麻烦。UVM 提供了一个类 uvm_sequence_library_cfg 来对 sequence library 进行配置。它一共有三个成员变量:

清单 6-112

systemverilog 复制代码
来源:UVM 源代码 
class uvm_sequence_library_cfg extends uvm_object; 
  `uvm_object_utils(uvm_sequence_library_cfg) 
  uvm_sequence_lib_mode selection_mode; 
  int unsigned min_random_count; 
  int unsigned max_random_count; 
... 
endclass 

通过配置如上三个成员变量,并将其传递给 sequence library 就可对 sequence library 进行配置:

清单 6-113

systemverilog 复制代码
文件:src/ch6/section6.8/6.8.4/cfg/my_case0.sv 
 85 function void my_case0::build_phase(uvm_phase phase); 
 86    uvm_sequence_library_cfg cfg; 
 87    super.build_phase(phase); 
 88 
 89    cfg = new("cfg", UVM_SEQ_LIB_RANDC, 5, 20); 
 90 
 91    uvm_config_db#(uvm_object_wrapper)::set(this, 
 92                                    "env.i_agt.sqr.main_phase", 
 93                                    "default_sequence", 
 94                                    simple_seq_library::type_id::get()); 
 95    uvm_config_db#(uvm_sequence_library_cfg)::set(this, 
 96                                    "env.i_agt.sqr.main_phase", 
 97                                    "default_sequence.config", 
 98                                    cfg); 
 99 endfunction 

除了使用专门的 cfg 外,还有一种简单的配置方法是使用代码清单 6-7 的方式启动 sequence,在对 sequence library 进行实例化后,对其中的变量进行赋值:

清单 6-114

systemverilog 复制代码
文件:src/ch6/section6.8/6.8.4/start/my_case0.sv 
 85 function void my_case0::build_phase(uvm_phase phase); 
 86   simple_seq_library seq_lib; 
 87   super.build_phase(phase); 
 88 
 89   seq_lib = new("seq_lib"); 
 90   seq_lib.selection_mode = UVM_SEQ_LIB_RANDC; 
 91   seq_lib.min_random_count = 10; 
 92   seq_lib.max_random_count = 15; 
 93   uvm_config_db#(uvm_sequence_base)::set(this, 
 94                                    "env.i_agt.sqr.main_phase", 
 95                                    "default_sequence", 
 96                                    seq_lib); 
 97 endfunction 

sequence 机制:

  • 激励产生与驱动分离 :从 driver 中剥离 gen_pkt 的思想演进到 sequence 机制,是 sequence 存在的根本理由;sequencer 启动 sequence 并向 driver 交付 transaction,等价于在 driver 内调用 gen_pkt
  • 仲裁与独占 :同一 sequencer 可并行启动多个 sequence,靠 transaction/sequence 优先级 + 仲裁算法(SEQ_ARB_FIFO/WEIGHTED/RANDOM/STRICT_FIFO/STRICT_RANDOM/USER)裁决;lock/grab 实现独占,is_relevant/wait_for_relevant 实现主动失效。
  • 宏的本质uvm_do 系列 8 个宏最终都归结为 uvm_do_on_pri_withuvm_create+uvm_senduvm_rand_send、以及最底层的 start_item/finish_item 提供了从"最省事"到"最灵活"的不同层次;pre_do/mid_do/post_do 在宏内部的关键节点插入钩子。
  • 进阶 :嵌套 sequence 实现重用;sequence 内可用 rand 变量(注意命名不要与 transaction 字段同名);sequencer 接受类型须匹配,uvm_sequence_item 可混发多类型;p_sequencer/m_sequenceruvm_declare_p_sequencer 解决跨类访问 sequencer 成员;base sequence 派生时 p_sequencer 无需重复声明。
  • virtual sequence :用 virtual sequencer 持有各真实 sequencer 指针,由 virtual sequence 统一调度、同步多个数据通道,并集中控制 objection、减少 config_db 语句量;务必注意 fork...join_none 会令父 sequence 提前结束而"杀死"子进程,须用 wait forkfork...join 等待。
  • config_db 与 sequence :sequence 通过 get_full_name() 配合 uvm_config_db::get(null, ...) 取参,用 uvm_config_db::set(uvm_root::get(), ...) 设参,wait_modified 用于 task phase 中监听参数动态变化。
  • response 反馈put/get_response(含直接 item_done(rsp))、多 response 与队列溢出风险、response_handler 解耦收发进程、以及"借 req 回写字段"的另类 response;并支持 rsp 与 req 类型不同(需双参数化 driver/sequencer/sequence)。
  • sequence library :将多个 sequence 注册为一个库,按 UVM_SEQ_LIB_RAND/RANDC/ITEM/USER 算法随机选择执行,通过 min/max_random_countselection_modeuvm_sequence_library_cfg 配置执行次数与策略。
相关推荐
Salt & Light2 天前
UVM 字段自动化 Field Automation:让打印、复制、比较自动化
学习·systemverilog·uvm
fei_sun8 天前
UVM中的TLM1.0通信
运维·服务器·uvm
liuluyang53015 天前
UVM学习-2
uvm
liuluyang53016 天前
UVM入门和进阶实验2
uvm
啄缘之间16 天前
5.1 uvm‑component 的层级结构管理? new (name,parent) 参数
开发语言·笔记·学习·ic·uvm·sv
happyDogg_1 个月前
uvm_root详解
uvm·数字验证
happyDogg_1 个月前
UVM 中 virtual 关键字的使用详解
virtual·uvm·interface·数字验证
啄缘之间3 个月前
8.【学习】工业级详细接口约束&覆盖率
开发语言·笔记·学习·uvm·sv
Ether IC Verifier3 个月前
SystemVerilog 数据类型详解
php·systemverilog·uvm·ic验证