UVM——Fun with UVM Sequences - Coding and Debugging

0. 介绍

​ 今天早上,发现Mentor Verification给我发了一封推送邮件,说有关于验证的最新研究。我是要做验证工程师的男人,看到这,能不打开吗,找了一篇关于UVM sequence的文章—— Fun with UVM Sequences - Coding and Debugging。哇,这个外国人写得确实不错,一上午拜读,也自己跑了其中的一些东西,顺便也整理了一下这篇文章的东西。原文都是复制的,中文的地方是我自己的理解。

CREATING A SEQUENCE

​ The test below creates a sequence inside a fork/join_none. There will be four sequences running in parallel. Each sequence has a LIMIT variable set and starts to run at the end of the fork/join_none. Once all of the forks are done, the test completes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class test extends uvm_test;
`uvm_component_utils(test)

my_sequence seq;
...
task run_phase(uvm_phase phase);
phase.raise_objection(this);
for (int i = 0; i < 4; i++) begin
fork
automatic int j = i; //automatic自动变量,每隔进程中都不同
seq = new($sformatf("seq%0d", j));
seq.LIMIT = 25 * (j+1);
seq.start(sqr);
join_none
end
wait fork;
phase.drop_objection(this);
endtask
endclass

RUNNING A SEQUENCE — CREATING AND SENDING A SEQUENCE ITEM

​ Within the for-loop, a transaction object is constructed by calling new () or using the factory. Then start_item is called to begin the interaction with the sequencer. At this point the sequencer halts the execution of the sequence until the driver is ready. Once the driver is ready, the sequencer causes ‘start_item’ to return. Once start_item has returned, then this sequence has been granted permission to use the driver. Start_item should really be called “REQUEST_TO_SEND”. Now that the sequence has permission to use the driver, it randomizes the transaction, or sets the data values as needed. This is the so-called “LATE RANDOMIZATION” that is a desirable feature. The transactions should be randomized as close to executing as possible, that way they capture the most recent state information in any constraints.

sequence执行start_item(tr),等待driver准备好接受数据,如果driver准备好了,start_item(tr)结束;finish_item(tr)是sequence向driver发送数据,如果driver接受了数据并且执行完,调用了seq_item_port.item_done(),那么finish_item(tr)返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class my_sequence extends uvm_sequence#(transaction);
`uvm_object_utils(my_sequence)

transaction t;
int LIMIT;
...
task body();
for (int i = 0; i < LIMIT; i++) begin
t = new(“t”);
start_item(t);///
t.data = i+1;
if (!t.randomize())
`uvm_fatal(get_type_name(), "Randomize FAILED")
finish_item(t);//
end
endtask
endclass

EXECUTING A SEQUENCE ITEM — THE DRIVER

​ The driver code is relatively simple. It derives from a uvm_driver and contains a run_phase. The run_phase is a thread started automatically by the UVM core. The run_phase is implemented as a forever begin-end loop. In the begin-end block the driver calls seq_item_port.get_next_item (t). This is a task which will cause execution in the sequencer – essentially asking the sequencer for the next transaction that should be executed. It may be that no transaction is available, in which case this call will block. (There are other non-blocking calls that can be used, but are beyond the scope of this article, and not a recommended usage). When the sequencer has a transaction to execute, then the get_next_item call will unblock and return the transaction handle in the task argument list (variable ‘t’ in the example below). Now the driver can execute the transaction.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class driver extends uvm_driver#(transaction);
`uvm_component_utils(driver)

transaction t;
...
task run_phase(uvm_phase phase);
forever begin //这里用的是forever,我之前用while(1)
seq_item_port.get_next_item(t);
`uvm_info(get_type_name(),
$sformatf("Got %s", t.convert2string()), UVM_MEDIUM)
#(t.duration); // Execute...
seq_item_port.item_done();
end
endtask
endclass

有用-CONTROLLING OTHER SEQUENCES

Sequences can have handles to other sequences; after all, a sequence is just a class object with data members, and a “task body()”, which will run as a thread.

Virtual Sequences

The so-called “virtual sequence” – a sequence which may not generate sequence items, but rather starts sequences on other sequencers. This is a convenient way to create parallel operations from one control point.

A virtual sequence simply has handles to other sequences and sequencers. It starts them or otherwise manages them. The virtual sequence may have acquired the sequencer handles by being assigned from above, or by using a configuration database lookup, or other means. It may have constructed the sequence objects, or have acquired them by similar other means. The virtual sequence is like a puppet master, controlling other sequences.

这里说可以用config机制来获得sequencer,那么就是说可以在virtual sequence写成如下:

但我写成下面这样出错:还没解决

1
UVM_FATAL @ 0: reporter@@vseq.seq0 [SEQ] neither the item's sequencer nor dedicated sequencer has been supplied to start item in vseq.seq0
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
51
52
53
54
55
56
57
58
59
60
class sequence0 extends uvm_sequence#(transaction);
`uvm_object_utils(sequence0);
function new(string name="sequence0");
super.new(name);
endfunction

task body();
transaction tr;
repeat(5) begin
`uvm_do(tr);
end
endtask

endclass

class vsequence extends uvm_sequence#(transaction);//virtual sequence
`uvm_object_utils(vsequence);

function new(string name="vsequence");
super.new(name);
endfunction

task pre_body();
if(starting_phase!=null)
starting_phase.raise_objection(this);
endtask
task body();
sequence0 seq0;
sequencer sqr;
string sqr_name;
if(!uvm_config_db#(sequencer)::get(null,get_full_name(),"sqr",sqr))
`uvm_fatal(get_type_name(),"Failed get sequencer");
`uvm_info(get_type_name(),"vsequence start a seq",UVM_LOW);
`uvm_do_on(seq0,sqr);
endtask
task post_body();
if(starting_phase!=null)
starting_phase.drop_objection(this);
endtask
endclass

class case0 extends base_case;
`uvm_component_utils(case0);
function new(string name="case0",uvm_component parent=null);
super.new(name,parent);
endfunction

function void build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db#(sequencer)::set(null,"*","sqr",env.sqr);
endfunction

task run_phase(uvm_phase phase);
vsequence vseq;
super.run_phase(phase);
vseq=new("vseq");
vseq.starting_phase=phase;
vseq.start(null); //virtual sequence 没有sequencer
endtask
endclass

所以我就改成了另一个方法,在virtual sequence中,通过top.find(“*.env.sqr”)找到env上的sequencer,使seq在他上面启动,注意find返回类型使uvm_component,需要用cast转换成sequencer类型。

代码如下,base_test 不变。

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
class sequence0 extends uvm_sequence#(transaction);
`uvm_object_utils(sequence0);

function new(string name="sequence0");
super.new(name);
endfunction

task body();
transaction tr;
repeat(5) begin
`uvm_do(tr);
end
endtask

endclass

class vsequence extends uvm_sequence#(transaction);
`uvm_object_utils(vsequence);
uvm_component component_h;
sequencer sequencer_h;
function new(string name="vsequence");
super.new(name);
component_h = uvm_top.find("*.env.sqr");//找到启动sequencer
if(!$cast(sequencer_h,component_h))//类型转换
`uvm_fatal(get_type_name(),"cast errror");
endfunction

task pre_body();
if(starting_phase!=null)
starting_phase.raise_objection(this);
endtask

task body();
sequence0 seq0;
`uvm_do_on(seq0,sequencer_h);//启动seq
endtask

task post_body();
if(starting_phase!=null)
starting_phase.drop_objection(this);
endtask

endclass

今天(第二天)我不死心有用config_source_db来试,还是不行,上午看UVM UG的时候发现上面就有给配置sequencer的例子,但是他跟我用的一样,那么我确信确实可以这样配置,然后我又研究了一下,突然想到我的sequencer是有参数的,我是不是也应该加上,试了一下,果然可以,开心,哈哈。

1
2
3
4
5
6
7
8
9
10
11
task sequence0::body();
sequence0 seq0;
sequencer#(transaction) sequencer_h;
uvm_config_db#(sequencer#(transaction))::get(null,get_full_name(),"sqr",sequencer_h);
`uvm_info(get_type_name(),"vsequence start a seq",UVM_LOW);
`uvm_do_on(seq0,sequencer_h);
endtask
function void case0::connect_phase(uvm_phase phase);
super.connect_phase(phase);
uvm_config_db#(sequencer#(transaction))::set(null,"*","sqr",env.sqr);
endfunction

然而,我又理解错了,我之所以这次对并不是因为加了参数,而且这里也不应该给sequencer加参数,它是我自己定义的类,不是个参数类,会报警告,真正原因是我把set的语句放在的build_phase之后的conncet_phase中,之前我写在case0的build_phase中,至于为什么可以不太明白,难道是因为case0的build先执行?

前面的代码中,我使用了virtual sequence,但其实不用virtual sequence,直接在sequence启动就可以。

当然也可以用一个virtual sequencer来传递子sequence的sequencer句柄。

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
// 2019-08-27
class vsequencer extends uvm_sequencer#(transaction);
sequencerA seqA;
sequencerB seqB;
endclass
class base_test extends uvm_test;
enviroment env
vsquencer vsqr;
...
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
vsqr.seqA=env.i_agtA.sqr;
vsqr.seqB=env.i_agtB.sqr;
endfunction
endclass
class vsequence extends uvm_sequence#(transaction)
task body()
sequenceA seqA;
sequenceB seqB;
seqA.start(p_sequencer.seqA);
seqB.start(p_sequencer.seqB);
endtask
endclass
class case0 extends base_test;
function void build_phase(uvm_phase phase)
uvm_config_db#(uvm_object_wrapper)::set(this,"vsqr.run_phase",
"default_sequence",vsequence::type_id::get());
endfunction
endclass

A virtual sequence might look like:

1
2
3
4
5
6
7
8
9
sequenceA_t sequenceA;
sequenceB_t sequenceB;
sequencerA sqrA;
sequencerB sqrB;

task body();
sequenceA.start(sqrA);
sequenceB.start(sqrB);

WRITING A SELF-CHECKING SEQUENCE

​ A self-checking sequence is a sequence which causes some activity and then checks the results for proper behavior. The simplest self-checking sequence issues a WRITE at an address, then a READ from the same address. Now the data read is compared to the data written. In some ways the sequence becomes the GOLDEN model.

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
class write_read_sequence extends my_sequence;
`uvm_object_utils(write_read_sequence)
...

task body();
for (int i = 0; i < LIMIT; i++) begin
t = new($sformatf("t%0d", i));
start_item(t);
if (!t.randomize())
`uvm_fatal(get_type_name(), "Randomize FAILED")
// 可以用get_type_name()这里就返回write_read_sequence类名
t.rw = WRITE;
finish_item(t);
end

for (int i = 0; i < LIMIT; i++) begin
t = new($sformatf("t%0d", i));
start_item(t);
if (!t.randomize())
`uvm_fatal(get_type_name(), "Randomize FAILED")
t.rw = READ;
t.data = 0;
finish_item(t);

// Check
if (t.addr != t.data) begin
`uvm_info(get_type_name(), $sformatf("Mismatch.
Wrote %0d, Read %0d",
t.addr, t.data), UVM_MEDIUM)
`uvm_fatal(get_type_name(), "Compare FAILED")
end
end
endtask
endclass

WRITING A TRAFFIC GENERATOR SEQUENCE

​ A video traffic generator can be written to generate a stream of background traffic that mimics or models the amount of data a video display might require. There is a minimum bandwidth that is required for video. That calculation will change with the load on the interface or bus, but a simplistic calculation is good enough for this example. The video traffic will generate a “screen” of data 60 times a second. Each screen will have 1920 by 1024 dots. Each dot is represented by a 32 bit word. Using these numbers, the traffic generator must create 471MB per second.

这算是一个sequence的小例子

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
class video extends my_sequence;
`uvm_object_utils(video)

int xpixels = 1920;
int ypixels = 1024;
int screendots;
int rate;
bit [31:0] addr;

int x;
int y;

video_transaction t;

task body();
screendots = xpixels * ypixels;
rate = 1_000_000_000 / (60 * screendots);
//周期是1ns,1s内10^9个周期,每隔rate周期发送一个数据。
forever begin
addr = 0;
for (x = 0; x < xpixels; x++) begin
for (y = 0; y < ypixels; y++) begin
t = new($sformatf("t%0d_%0d", x, y));
start_item(t);
if (!t.randomize())
`uvm_fatal(get_type_name(), "Randomize FAILED")
t.rw = WRITE;
t.addr = addr++;
t.duration = rate;
finish_item(t);
end
end
end
endtask
endclass

有用-WRITING SEQUENCES THAT ARE SYNCHRONIZED WITH EACH OTHER

​ Sequences will be used to synchronize other sequences (so called virtual sequences). Often two sequences need to have a relationship between them formalized. For example they cannot enter their critical regions together – they must go single-file. Or they can only run after some common critical region has passed.

The code below declares two sequences which are to be synchronized (synchro_A_h and synchro_B_h). It also declares a synchronizer. There is nothing special about these classes – they have simply agreed to use some technique to be synchronized.

在virtual sequence中通过synchronizer来控制synchro_A和synchro_B。

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
class vsequence extends uvm_sequence;
synchro synchro_A_h;
synchro synchro_B_h;
synchronizer s;

task body();
s = new();
//declares two sequences which are to be synchronized
synchro_A_h = new("synchroA");
synchro_B_h = new("synchroB");

//The synchronized sequences get a handle to
//the synchronizer and a starting address.
synchro_A_h.s = s;
synchro_A_h.start_addr = 2;
synchro_B_h.s = s;
synchro_B_h.start_addr = 2002;
//The synchronizer control is rather simple.
//It just says “GO” for 20 ticks and “STOP” for 100 ticks.
fork //通过控制s.state来控制两个seq的运行
forever begin
#100;
s.state = GO;//运行seq
#20;
s.state = STOP;//停止运行
end
join_none
//The synchronized sequences are started.
//They run to completion and then simply get restarted.
//They run forever.
fork//不停地尝试启动,当s.state为GO的使用产生tr
forever begin
synchro_A_h.start(sqr);
end
forever begin
synchro_B_h.start(sqr);
end
join_none
endtask
endclass

The simple synchronizer with two states — GO and STOP.

1
2
3
4
5
typedef enum bit { STOP, GO } synchro_t;

class synchronizer;
synchro_t state;
endclass

The class that uses a synchronizer to NOT execute until told to do so.

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
class synchro extends my_sequence;// 子sequence
`uvm_object_utils(synchro)

bit [31:0] start_addr;
bit [31:0] addr;
synchronizer s;

synchro_transaction t;

task body(); //子sequence中的body任务
forever begin
addr = start_addr;
// Is it safe?
while (s.state == STOP) begin //如果STOP则一直在这里阻塞
#10;
`uvm_info(get_type_name(), "Waiting...", UVM_MEDIUM)
end
//state为GO,跳过阻塞,发送seq
t = new($sformatf("t%0d", addr));
start_item(t);
if (!t.randomize())
`uvm_fatal(get_type_name(), "Randomize FAILED")
t.rw = WRITE;
t.addr = addr++;
finish_item(t);
end
endtask
endclass

In simulation, the sequence waits until the synchronizer is in the GO state. Once in the GO state, then the synchronized code generates a transaction using new, and then calls start_item/finish_item to execute it. After waiting for access to the driver and then executing, the synchronized sequence comes back to the top of the loop and checks the synchronizer state. It will either GO again, or STOP/WAIT

有用-IMPLEMENTING AN INTERRUPT SERVICE ROUTINE WITH SEQUENCES

Sequences will be used to provide an “interrupt service routine”. Interrupt service routines “sleep” until needed. This is a unique kind of sequence. In this example implementation it creates an “interrupt service transaction” and does start_item and finish_item. In this way, it can send that ISR transaction handle to the driver. The driver is then going to hold that handle UNTIL there is an interrupt.

As part of the drivers’ job of handling the SystemVerilog Interface, it will handle the interrupts. In this case, handling the interrupt means that some data is put into the “held handle” and then the handle is marked done. Meanwhile, the interrupt service sequence has been waiting for the transaction to be marked DONE. In some parlance(说法) this is known as ITEM REALLY DONE. In the UVM, there are other mechanisms for handling this kind of thing, but they are unreliable and more complicated than this solution.

就是说,driver驱动transaction到接口,driver怎么知道DUT执行完了呢,这需要driver从接口中读个数据,返回给sequence,它显示DUT是否执行完,如果执行完那么sequence就可以产生下一个transaction。

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
class interrupt_transaction extends transaction;
`uvm_object_utils(transaction)
int VALUE;
bit DONE; //这个DONE的值是从driver返回的。在sequence中做判断
endclass

class interrupt_sequence extends my_sequence;
`uvm_object_utils(interrupt_sequence)

interrupt_transaction t;

task body();
forever begin
t = new("isr_transaction");
start_item(t);
finish_item(t);
wait(t.DONE == 1);//t还是指向刚才发送的transaction,
//等待中断发生并执行完
`uvm_info(get_type_name(), $sformatf("Serviced %0d",
t.VALUE), UVM_MEDIUM)
end
endtask
endclass

class driver extends uvm_driver#(transaction);
`uvm_component_utils(driver)

transaction t;
interrupt_transaction isr;
bit done;
int value;

bit [31:0] mem[1920*1024];

task interrupt_service_routine(interrupt_transaction isr_h);
`uvm_info(get_type_name(), "Setting ISR", UVM_MEDIUM)
done = 0;
isr_h.DONE = 0;
wait(done == 1); //等待中断发生
isr_h.VALUE = value; //中断服务程序运行。
isr_h.DONE = 1; //运行完,返回DONE,那么sequence中可以发送下一个transaction
endtask

task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(t);

if ($cast(isr, t)) begin//如果是isr transaction,则进入ISR,ISR是否执行还要看后面的
//transaction是否触发中断。
fork
interrupt_service_routine(isr);//SIR
join_none
end
else begin//如果是普通 transaction
...
// REGULAR driver processing
...
if (AN INTERRUPT OCCURS) begin //如果发生了中断,那么进行一些操作
done = 1; // 并将done置一,上面的ISR就会执行完。
value = mem[t.addr];
end
//如果没有发生中断,那么ISR不会执行
end
seq_item_port.item_done();
end
endtask
endclass

有用-SEQUENCES WITH “UTILITY LIBRARIES”

Sequence “utility libraries” will be created and used. Utility libraries are simple bits of code that are useful for the sequence writer – helper functions or other abstractions of the verification process.

The open_door sequence below does just as its name implies. It opens the door to the sequencer and driver. Outside calls can now be made at will using the sequence object handle (seq.read () and seq.write () for example).

这个很好用,也是一种启动sequence的方式。

在sequence外,通过sequence句柄调用sequence中发送transaction的任务。

原理是:之前把发送transaction的语句写在了body中,sequence启动后,自动调用body,所以我们也可以把发送transaction语句写在其他任务中,让body为空。我们在sequence启动后,手动调用这些任务发送transaction。

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
class open_door extends my_sequence;
`uvm_object_utils(open_door)

read_transaction r;
write_transaction w;

task read(input bit[31:0]addr, output bit[31:0]data);
r = new("r");
start_item(r);
if (!r.randomize())
`uvm_fatal(get_type_name(), "Randomize FAILED")
r.rw = READ;
r.addr = addr;
finish_item(r);
data = r.data;
endtask

task write(input bit[31:0]addr, input bit[31:0]data);
w = new("w");
start_item(w);
if (!w.randomize())
`uvm_fatal(get_type_name(), "Randomize FAILED")
w.rw = WRITE;
w.addr = addr;
w.data = data;
finish_item(w);
endtask

task body();
`uvm_info(get_type_name(), "Starting", UVM_MEDIUM)
wait(0);
`uvm_info(get_type_name(), "Finished", UVM_MEDIUM)
endtask
endclass

The open_door is constructed and then started using normal means. Then a test program can issue reads and writes simply as in the RED lines below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
open_door open_door_h;

open_door_h = new("open_door");
fork
open_door_h.start(sqr);
begin
bit [31:0] rdata;
for (int i = 0; i < 100; i++) begin
open_door_h.write(i, i+1);
open_door_h.read(i, rdata);
if ( rdata != i+1 ) begin
`uvm_info(get_type_name(), $sformatf("Error: Wrote '%0d', Read '%0d'",
i+1, rdata), UVM_MEDIUM)
//`uvm_fatal(get_type_name(), "MISMATCH");
end
end
end
join_none

下面是我自己写的,在case中启动sequence,并调用其函数

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
51
52
53
54
55
56
57
58
59
60
class sequence1 extends uvm_sequence#(transaction);

`uvm_object_utils(sequence1);
function new(string name="sequence1");
super.new(name);
endfunction
task write();
transaction tr;

`uvm_info(get_type_name(),"write",UVM_LOW)
`uvm_do(tr);
endtask
task read();

transaction tr;
`uvm_info(get_type_name(),"read",UVM_LOW)
`uvm_do(tr);
endtask
task pre_body();
if(starting_phase != null)
starting_phase.raise_objection(this);
`uvm_info(get_type_name(),"pre_body",UVM_LOW)
endtask
task body();
`uvm_info(get_type_name(),"body",UVM_LOW)
wait(0);

endtask
task post_body();
`uvm_info(get_type_name(),"post_body",UVM_LOW)
if(starting_phase!=null)
starting_phase.drop_objection(this);
endtask
endclass
//在case中启动启动
class case0 extends base_test;
`uvm_component_utils(case0);

function new(string name="case0",uvm_component parent=null);
super.new(name,parent);
endfunction

task run_phase(uvm_phase phase);
sequence1 seq1;
seq1=new("seq1");
fork
begin
seq1.starting_phase = phase;
seq1.start(env0.sqr);
end
begin
for(int i=0;i<10;i++) begin
seq1.write();//手动调用任务发送transaction
seq1.read();
end
end
join_none
endtask
endclass

结果如下:

1
2
3
4
5
6
7
8
9
10
11
27:UVM_INFO seq/sequence1.sv(13) @ 0: uvm_test_top.env0.sqr@@seq1 [sequence1] write
28:UVM_INFO seq/sequence1.sv(25) @ 0: uvm_test_top.env0.sqr@@seq1 [sequence1] pre_body
29:UVM_INFO seq/sequence1.sv(28) @ 0: uvm_test_top.env0.sqr@@seq1 [sequence1] body
54:UVM_INFO seq/sequence1.sv(19) @ 0: uvm_test_top.env0.sqr@@seq1 [sequence1] read
79:UVM_INFO seq/sequence1.sv(13) @ 0: uvm_test_top.env0.sqr@@seq1 [sequence1] write
....
454:UVM_INFO seq/sequence1.sv(19) @ 0: uvm_test_top.env0.sqr@@seq1 [sequence1] read
479:UVM_INFO seq/sequence1.sv(13) @ 0: uvm_test_top.env0.sqr@@seq1 [sequence1] write
504:UVM_INFO seq/sequence1.sv(19) @ 0: uvm_test_top.env0.sqr@@seq1 [sequence1] read
529:UVM_INFO seq/sequence1.sv(33) @ 1000000: uvm_test_top.env0.sqr@@seq1 [sequence1] post_body
543:[sequence1] 23

CALLING C CODE FROM SEQUENCES

这个就跟在SV中调用C一样,通过“DPI-C”,没什么好说的。

CALLING SEQUENCES FROM C CODE

这个光提了一下没讲。

REFERENCES

[1] SystemVerilog, 1800-2017 - IEEE Standard for SystemVerilog–Unified Hardware Design, Specification, and Verification Language

[2] UVM LRM: IEEE Standard for Universal Verification Methodology Language Reference Manual

All source code is available from the author. Contact rich_edelman@mentor.com for access or download from the Verification Academy.

This article was previously presented at DVCon US 2019.


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!