UVM——tb0-alu验证平台

更新

​ 09-28 之前遇到的问题解决了,可以生成激励、正确自检和收集覆盖率。

​ 代码 h.add_coverage_modified

0. 介绍

这几天为了准备面试,基于自己的思路写了一个验证平台,DUT是个ALU。

这个验证平台是逐步完善的,不断的迭代,最终可以测试ALU的计算,这里介绍的是添加了scoreboard之后可以进行随机激励生成、自检、覆盖率采样的版本。详细代码在链接里。

代码地址:d.add_coverage文件夹

1. 验证平台组成

上图中agent是复用的,通过is_active变量的值来决定要例化哪些组件。

在接口alu_bfm中进行时钟生成、reset任务、monitor采样、driver驱动等

2. DUT介绍

这里的ALU用的是《UVMPrimer》书中的DUT。

端口如下:

操作指令的编码如下:

其实在生成激励的时候,只生成了add、and、xor指令,其他的通过设置权重,不让他们生成,因为这个验证平台还是稍微优点问题的,以后有机会改一下。

这样的话它的覆盖率总是会比较低,不过权当作搭建tb的练习了。

接口的时序:

​ start信号为高,输入op、A、B有效,DUT在上升沿采样这些数据。当验证平台采样到DUT输出done为高时,说明计算结束,tb将start拉低(可以在下降沿检测done是否为高,如果为高,那么在下一个上升沿start拉低),done信号只持续一个时钟周期。

覆盖率采集:

  1. 所有操作的输入都是0;
  2. 所有操作的输入都是1;
  3. reset后执行所有操作;
  4. 执行所有操作后reset;
  5. 连续两个周期执行同一个指令;
  6. 单周期指令与多周期的乘法指令间切换

3. 激励生成

激励是在sequence中生成的。

在sequence中控制objection,pre_body中raise_objection,post_body中drop_objection。

也可以控制随机约束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
task pre_body();
if(starting_phase != null)
starting_phase.raise_objection(this);
`uvm_info("sequence0","pre_body",UVM_LOW)
endtask
task body();
`uvm_info("sequence0","body",UVM_LOW)
repeat(50) begin
//`uvm_do(tr);
//tr=new();
`uvm_create(tr);
//tr.con_op.constraint_mode(0);//控制随机约束
//tr.con_data.constraint_mode(0);
assert(tr.randomize);
`uvm_send(tr);
end
#1000;
endtask
task post_body();
`uvm_info("sequence0","post_body",UVM_LOW)
if(starting_phase!=null)
starting_phase.drop_objection(this);
endtask

4. driver驱动激励到接口

在接口中定义驱动激励的任务drive_one_pkg,

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
task drive_one_pkg(transaction tr);
if(tr.op == rst_op) begin//先判断是否是复位指令
@(posedge clk);
reset_n = 1'b0;
start = 1'b0;
@(posedge clk);
#1;
reset_n = 1'b1;
end
else begin
@(negedge clk);//下降沿将op、A、B驱动到接口
op = tr.op;
A = tr.A;
B = tr.B;
start = 1'b1;
if(tr.op == no_op) begin // 如果是no_op,不进行操作,那么下一个时钟沿直接将start拉低,
@(posedge clk); // 表示不进行计算
#1;
start = 1'b0;
end
else begin//进行计算的指令
do
@(negedge clk);//this is negedge
while(done==0);//下降沿时候判断计算是否结束,
tr.result = result;//结束了,将计算结果返回到tr,这个值可以传给sequence,用来控制激励生成。
start = 1'b0;
end
end
endtask

我们在driver的run_phase中通过虚接口调用这个函数就行了。

driver中,可以在从sequencer取transaction之前,调用接口的reset任务复位DUT。

1
2
3
4
5
6
7
8
9
10
11
task driver::run_phase(uvm_phase phase);
super.run_phase(phase);
`uvm_info("driver","run_phase",UVM_LOW);
bfm.reset_alu();//reset DUT
while(1) begin //循环保证我们可以不断的向sequencer取transaction
seq_item_port.get_next_item(req);
//req.print();
bfm.drive_one_pkg(req); //调用接口的驱动任务
seq_item_port.item_done();
end
endtask

5. 输入monitor采样接口数据

输入monitor的采样是在接口中进行的,并将采样到的值,通过调用输入monitor的任务,从port传给model。

在接口中的采样代码如下,通过always块,在每个时钟沿采样:

1. 我们需要判断DUT接口上的值是否是有效的输入,通过判断start信号是否为高;

2. 另一个防止对同一个计算数据(op、A、B)采样了多次,我们需要判断当前的指令是否是新的指令,start下降沿表示一条指令执行完成,那么接下来又采样到start为高,就说明此时是新的指令了。

接口中的always如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
command_monitor command_monitor_h;//需要传递一个输入monitor的句柄,这样可以让我们在接口中调用
// monitor的任务

always@(posedge clk ) begin
static bit new_command=0;
if(!start) begin
new_command = 1'b1;
end
else if(start) begin
if(new_command) begin
$display("[collect ****]");
$display("op is %d",op);
command_monitor_h.collect_one_pkg(A,B,op2enum());
new_command = (op2enum()==no_op);
end
end
end

monitor中的操作:

  1. 将句柄传递给接口
  2. 通过port发送给model
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function void connect_phase(uvm_phase phase); //将句柄传递给接口
bfm.command_monitor_h=this;
endfunction

task collect_one_pkg(byte A,byte B,operation_t op);
transaction cmd_mon_tr;
cmd_mon_tr=new("tr");
cmd_mon_tr.A=A;
cmd_mon_tr.B=B;
cmd_mon_tr.op=op;
ap.write(cmd_mon_tr);//通过port发送给model
`ifdef CMD_MON_PRINT
cmd_mon_tr.print();
`endif
endtask

6. 参考模型接受输入采样,产生预测输出

我的参考模型使用SV实现的

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
class model extends uvm_component;

`uvm_component_utils(model);

virtual alu_bfm bfm;

uvm_blocking_get_port#(transaction) port;
uvm_analysis_port#(result_transaction) ap;

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

function void build_phase(uvm_phase phase);
super.build_phase(phase);
port = new("port",this);
ap = new("ap",this);
if(!uvm_config_db#(virtual alu_bfm)::get(this,"","bfm",bfm))
`uvm_fatal("model","Faile get bfm!!!");
endfunction

task run_phase(uvm_phase phase);
transaction model_get_tr;
result_transaction model_result_tr;
while(1) begin//在循环中不停地向输入monitor要数据。
port.get(model_get_tr);//从输入monitor中得到有效数据
`uvm_info("model","model get transaction!!!",UVM_LOW);
`ifdef MODEL_PRINT
`uvm_info("model","print model_get_tr",UVM_LOW);
model_get_tr.print();
`endif
model_result_tr = new("model_result_tr");
@(posedge bfm.done);//也可以不用这句话,因为我在scoreboard中,将左右的有效预测都存在了一个
//队列里。
case(model_get_tr.op)
add_op:model_result_tr.result=model_get_tr.A+model_get_tr.B;
and_op:model_result_tr.result=model_get_tr.A&model_get_tr.B;
xor_op:model_result_tr.result=model_get_tr.A^model_get_tr.B;
mul_op:model_result_tr.result=model_get_tr.A*model_get_tr.B;
endcase
ap.write(model_result_tr);//将产生的预测值发给scoreboard
`ifdef MODEL_PRINT
`uvm_info("model","print model_result_tr",UVM_LOW);
model_result_tr.print();
`endif

end
endtask

endclass

7. 输出monitor采样DUT真实值

输出monitor和输入monitor差不多。

在接口中采样输出:

1
2
3
4
5
6
result_monitor  result_monitor_h;

always@(posedge clk) begin
if(done)//如果done有效就采样,因为done只持续一个周期,不会存在同一个结果采样多次的情况。
result_monitor_h.collect_one_pkg(result);
end

monitor中:

1
2
3
4
5
6
7
8
9
10
task collect_one_pkg(shortint result);
result_transaction result_mon_tr;
result_mon_tr=new("result_tr");
result_mon_tr.result=result;
ap.write(result_mon_tr);
`ifdef RESULT_MON_PRINT
`uvm_info("result_monitor","print result_mon_tr",UVM_LOW);
result_mon_tr.print();
`endif
endtask

8. scoreboard中比较预测值和真实值

因为参考模型的计算不需要是时钟周期的,所以可以看成拿到输入立马可以计算出预测值,在scoreboard将所哟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
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
class scoreboard extends uvm_scoreboard;

`uvm_component_utils(scoreboard);

result_transaction exp_queue[$];

uvm_blocking_get_port#(result_transaction) rly_port;
uvm_blocking_get_port#(result_transaction) exp_port;

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

function void build_phase(uvm_phase phase);
super.build_phase(phase);
rly_port=new("rly_port",this);
exp_port=new("exp_port",this);
endfunction

task run_phase(uvm_phase phase);
result_transaction rly_tr;
result_transaction exp_tr;
result_transaction tmp_tr;
bit result;
super.run_phase(phase);
fork
while(1) begin//这个进程是将model的预测值存在队列中。
exp_port.get(exp_tr);
exp_queue.push_back(exp_tr);
`ifdef SCB_PRINT
exp_tr.print();
`endif
end
while(1) begin//当真实值到来的时候,从预测队列中pop一个预测值,与真实值比较
rly_port.get(rly_tr);//得到真实值,否则阻塞。
`ifdef SCB_PRINT
rly_tr.print();
`endif
if(exp_queue.size()>0) begin
tmp_tr=exp_queue.pop_front();//从头开始取预测值
result=rly_tr.compare(tmp_tr);
if(result)
`uvm_info("scoreboard","Compare SUCCESSLY!!!",UVM_LOW);
// else begin
// `uvm_error("scoreboard","Compared FAILED!!!");
// tmp_tr.print();
// rly_tr.print();
// end
end
else begin
`uvm_error("scoreboard","Get from DUT,while can not get from model");
`ifdef SCB_PRINT
rly_tr.print();
`endif
end
end
join

endtask

endclass

9. 覆盖率组件

覆盖率是用单独的组件来收集的,输出monitor中的analysis port端口会将采样的数据传给model和coverage。

我的coverage是继承自uvm_subscripter,这个类中有一个analysis_export的对象,自己写一个write函数,在coverage中定义覆盖组,在这个write函数中采集覆盖组。这样,每次输入monitor通过analysis_port传递数据的时候就会调用coverage中的write函数来采集覆盖率信息。

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
68
class coverage extends uvm_subscriber#(transaction);
`uvm_component_utils(coverage);

byte unsigned A;
byte unsigned B;
bit[2:0] op;

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

covergroup data;
all_op:coverpoint op{
ignore_bins all_op={rst_op,no_op};
}

a_cov:coverpoint A{
bins zeros = {'h00};
bins ones = {'hff};
bins others={['h01:'hfe]};
}

b_cov:coverpoint B{
bins zeros = {'h00};
bins ones = {'hff};
bins others={['h01:'hfe]};
}

a_b_op: cross a_cov,b_cov,all_op{
bins add_00= binsof(all_op) intersect {add_op} &&
(binsof(a_cov.zeros) || binsof(b_cov.zeros));
bins add_FF= binsof(all_op) intersect {add_op} &&
(binsof(a_cov.ones) || binsof(b_cov.ones));
bins and_00 = binsof(all_op) intersect {and_op} &&
(binsof(a_cov.zeros) || binsof(b_cov.zeros));
bins and_FF = binsof(all_op) intersect {and_op} &&
(binsof(a_cov.ones) || binsof(b_cov.ones));
bins xor_00 = binsof(all_op) intersect {xor_op} &&
(binsof(a_cov.zeros) || binsof(b_cov.zeros));
bins xor_FF = binsof(all_op) intersect {xor_op} &&
(binsof(a_cov.ones) || binsof(b_cov.ones));
bins mul_00 = binsof(all_op) intersect {mul_op} &&
(binsof(a_cov.zeros) || binsof(b_cov.zeros));
bins mul_FF = binsof(all_op) intersect {mul_op} &&
(binsof(a_cov.ones) || binsof(b_cov.ones));
ignore_bins others_only=binsof(a_cov.others) && binsof(b_cov.others);
}
endgroup

covergroup cmd;
coverpoint(op){
bins rst2op[]=(rst_op=>[add_op:mul_op]),(rst_op=>no_op);
bins op2rst[]=([add_op:mul_op]=>rst_op),(no_op=>rst_op);
bins op[]={[add_op:mul_op],no_op,rst_op};
bins twoops[]=([no_op:mul_op][*2]);
}
endgroup

function void write(transaction t);//重载write函数
A=t.A;
B=t.B;
op=t.op;
data.sample();//采集覆盖率
cmd.sample();//采集覆盖率
endfunction
endclass

10. top module

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
module top;

import uvm_pkg::*;
import tb_pkg::*;

`include "uvm_macros.svh"

alu_bfm bfm();
tinyalu DUT(.clk(bfm.clk),
.reset_n(bfm.reset_n),
.A(bfm.A),
.B(bfm.B),
.op(bfm.op),
.start(bfm.start),
.result(bfm.result),
.done(bfm.done)
);

initial begin
uvm_config_db#(virtual alu_bfm)::set(null,"*","bfm",bfm);
run_test();
end
initial begin
$vcdpluson;
end
endmodule

注意上面uvm_config_db设置虚接口的语句,一定要写在run_test()语句之前,要不然会出错。

1
UVM_FATAL comp/driver.sv(15) @ 0: uvm_test_top.env.i_agt.drv [driver] Failed get bfm!!!

验证平台运行

运行的脚本都写在Makefile文件中,运行时执行下面命令就行了。

1
2
3
make
make urg
make dve

make:会进行编译仿真,验证平台会生成随机激励、自检

make urg:会收集覆盖率信息,生成一个both文件,里面存折html格式的覆盖率报告。

make dve:会启动dve,查看仿真波形


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