注意:个人学习笔记,参考书籍《The UVM Primer》,最好看英文版本。
在第一章的Introduction中,我们引入了TinyALU的案例,这一章将从从传统的SystemVerilog测试台开始,搭建一个简单的Testbench。
TinyALU Testbench 需要执行以下操作:
•完全测试TinyALU的功能
•模拟RTL的所有线路和通过这些线路的路径
这是一种“覆盖优先”的方法。我们定义了我们想要涵盖的内容,并创建了一个testbench去cover。并创建一个自检测试台(Testbench),这样可以运行回归,不必手动检查结果。
覆盖目标(Coverage goals):
• Test all operations 测试所有的操作
• Simulate all zeros on an input for all operations 测试输入全0
• Simulate all ones on an input for all operations 测试输入全1
• Execute all operations after a reset 复位后执行所有操作运算
• Run a multiply after a single cycle operation 单周期运算后执行乘法运算
• Run a single cycle operation after a multiply 乘法运算后执行单周期运算
• Simulate all operations twice in a row 连续执行两次所有操作运算
如果我们已经测试了所有这些场景,并且没有收到任何错误,我们可以确定TinyALU正在工作。我们还将检查代码覆盖率是否达到100%。
将TinyALU测试台存储在一个Testbench文件中,该文件包含三个部分:激励,自检和覆盖。我们在文件中例化DUT,并用激励驱动其信号,同时用自检和覆盖块进行监控。
1、信号声明
首先,定义TinyALU的操作,方便后续能够轻松定义激励。然后,在测试台中对所有的信号进行声明:
module top;
typedef enum bit[2:0] {no_op = 3'b000, //使用enum枚举类型对TinyAlu操作定义
add_op = 3'b001,
and_op = 3'b010,
xor_op = 3'b011,
mul_op = 3'b100,
rst_op = 3'b111} operation_t;//DUT不使用111操作码,可添加为rst的枚举
byte unsigned A;
byte unsigned B;
bit clk;
bit reset_n;
wire [2:0] op;
bit start;
wire done;
wire [15:0] result;
operation_t op_set;
//采用byte和bit定义激励变量
assign op = op_set; //采用赋值语句将操作码应用于DUT的op总线
tinyalu DUT (.A, .B, .clk, .op, .reset_n, .start, .done, .result);
//实例化DUT,该特性将端口与信号进行匹配,无需两次键入单个名称
2、coverage block
我们将使用覆盖组来捕获功能覆盖。我们声明覆盖组,实例化它们,并将它们用于采样。
covergroup op_cov; //操作码覆盖
//op_cov覆盖组确保我们已经覆盖了所有操作以及它们之间可能的交互
coverpoint op_set {
bins single_cycle[] = {[add_op : xor_op], rst_op,no_op};//前边所定义的枚举类型bins
bins multi_cycle = {mul_op}; //乘法的操作码bins
bins opn_rst[] = ([add_op:mul_op] => rst_op);//执行完操作运算后复位
bins rst_opn[] = (rst_op => [add_op:mul_op]);//复位后执行所有的操作运算
bins sngl_mul[] = ([add_op:xor_op],no_op => mul_op);//单周期运算结束后执行乘法运算
bins mul_sngl[] = (mul_op => [add_op:xor_op], no_op);//乘法运算后执行单周期运算
bins twoops[] = ([add_op:mul_op] [* 2]);//所有操作运算连续执行两次
bins manymult = (mul_op [* 3:5]);//乘法运算多次执行 3 4 5
}
endgroup
covergroup zeros_or_ones_on_ops;
//zeros_or_ones_on_ops覆盖组检查数据端口上是否有全0和全1,以及是否使用这些组合测试了所有操作
all_ops : coverpoint op_set {
ignore_bins null_ops = {rst_op, no_op};} //忽略空操作运算
a_leg: coverpoint A {
bins zeros = {'h00}; //0
bins others= {['h01:'hFE]};//0和1之间
bins ones = {'hFF}; //1
}
b_leg: coverpoint B {
bins zeros = {'h00};
bins others= {['h01:'hFE]};
bins ones = {'hFF};
}
//交叉覆盖,binsof生成的结果可以进一步通过intersect关键字,选择关联值与所需值集相交的仓
op_00_FF: cross a_leg, b_leg, all_ops {
bins add_00 = binsof (all_ops) intersect {add_op} &&
(binsof (a_leg.zeros) || binsof (b_leg.zeros));
bins add_FF = binsof (all_ops) intersect {add_op} &&
(binsof (a_leg.ones) || binsof (b_leg.ones));
bins and_00 = binsof (all_ops) intersect {and_op} &&
(binsof (a_leg.zeros) || binsof (b_leg.zeros));
bins and_FF = binsof (all_ops) intersect {and_op} &&
(binsof (a_leg.ones) || binsof (b_leg.ones));
bins xor_00 = binsof (all_ops) intersect {xor_op} &&
(binsof (a_leg.zeros) || binsof (b_leg.zeros));
bins xor_FF = binsof (all_ops) intersect {xor_op} &&
(binsof (a_leg.ones) || binsof (b_leg.ones));
bins mul_00 = binsof (all_ops) intersect {mul_op} &&
(binsof (a_leg.zeros) || binsof (b_leg.zeros));
bins mul_FF = binsof (all_ops) intersect {mul_op} &&
(binsof (a_leg.ones) || binsof (b_leg.ones));
bins mul_max = binsof (all_ops) intersect {mul_op} &&
(binsof (a_leg.ones) && binsof (b_leg.ones));
ignore_bins others_only =
binsof(a_leg.others) && binsof(b_leg.others);
}
endgroup
3、采样
一旦定义了覆盖组,我们就需要声明、例化和采样
initial begin
clk = 0;
forever begin
#10;
clk = ~clk;//#20一个周期
end
end
op_cov oc; //声明
zeros_or_ones_on_ops c_00_FF;
initial begin : coverage
oc = new(); //例化
c_00_FF = new();
forever begin @(negedge clk); //负边沿进行采样
oc.sample(); //操作码采样
c_00_FF.sample(); //输入操作数采样
end
end : coverage
这是一个非常简单的覆盖模型。我们在每个时钟的负边缘检查TinyALU上的操作和数据输入,并记录我们看到的内容。
4、tester block
一般情况,我们会为每个覆盖目标编写一个定向测试。在TinyALU的情况下,这是可能的,但它需要更多的编码。相反,我们将创建随机激励。
我们将使用两个函数实现约束随机激励:get_op()和get_data():
function operation_t get_op(); //随机操作码
bit [2:0] op_choice;
op_choice = $random; //随机化
case (op_choice)
3'b000 : return no_op;
3'b001 : return add_op;
3'b010 : return and_op;
3'b011 : return xor_op;
3'b100 : return mul_op;
3'b101 : return no_op;
3'b110 : return rst_op;
3'b111 : return rst_op;
endcase // case (op_choice)
endfunction : get_op
function byte get_data(); //随机操作数
bit [1:0] zero_ones;
zero_ones = $random; //随机化
if (zero_ones == 2'b00)
return 8'h00;
else if (zero_ones == 2'b11)
return 8'hFF;
else
return $random;
endfunction : get_data
initial begin : tester
reset_n = 1'b0;
@(negedge clk);
@(negedge clk); //较少毛刺
reset_n = 1'b1;
start = 1'b0;
repeat (1000) begin //重复1000次
@(negedge clk);
op_set = get_op(); //获取随机操作码
A = get_data(); //获取随机操作数
B = get_data();
start = 1'b1;
case (op_set) // handle the start signal
no_op: begin
@(posedge clk);
start = 1'b0;
end
rst_op: begin
reset_n = 1'b0;
start = 1'b0;
@(negedge clk);
reset_n = 1'b1;
end
default: begin
wait(done);
start = 1'b0;
end
endcase // case (op_set)
end
$stop;
end : tester
此循环为TinyALU生成1000个事务。每次通过循环,我们都会从get_op()获得一个随机操作,从get_data()获得随机数据,将它们驱动到TinyALU,并处理启动信号的协议。
5、使用Scoreboard回路自检
Scoreboard循环检查实际TinyALU结果与预测结果。循环监视完成信号。当完成信号变高时,Scoreboard根据输入预测TinyALU输出,并检查输出是否正确:
always @(posedge done) begin : scoreboard
shortint predicted_result; //定义预测输出信号
#1;
case (op_set)
add_op: predicted_result = A + B;
and_op: predicted_result = A & B;
xor_op: predicted_result = A ^ B;
mul_op: predicted_result = A * B;
endcase // case (op_set)
if ((op_set != no_op) && (op_set != rst_op)) //确保进行操作运算
if (predicted_result != result)
$error ("FAILED: A: %0h B: %0h op: %s result: %0h",
A, B, op_set.name(), result); //错误输出打印
end : scoreboard
覆盖结果