在上一章中,我们创建了一个比较合理的验证平台,具有以下几个特点:
-
功能覆盖率
-
自检功能
-
带约束的随机激励
但是这个验证平台将所有模块文件都放在了一个文件里,这使得后期的修改、重用和debug都变得非常的困难。随着后期项目和设计的复杂演化,验证平台也将变得越来越弱,这是一个巨大的缺陷。等到了项目的后期,甚至一个小小的改动都将导致验证平台的崩溃。这样的验证平台也无法用于下一个设计里。
因此需要使用一种标准化方法来创建模块化的验证平台,每次加入新代码后,其功能就更强大,也可以很容易地加入新功能到验证平台。SystemVerilog的interface封装了验证平台中的port信号,让其在modules间和objects间共享这些信号。interface可以让我们很方便的共享信号,用它们来创建 BFM,就可以对简单的总线访问协议进行封装。
TinyALU BFM( Bus Functional Model)
tinyalu_bfm封装了所有TinyALU验证平台用到的信号,并提供了一个时钟,一个reset_alu( ) task还有一个向DUT发送指令的send_op( ) task。用定义module的方式来定义一个SystemVerilog interface。从使用 interface 关键字开始,在interface中定义信号。
BFM 提供了两个 task: reset_alu( )和 send_op( )。
reset_alu( ) task 用来拉低 reset 信号,等待几个时钟然后再次拉高。
send_op( ) task 用来向 ALU 发送指令并返回结果,展示了BFM是如何封装DUT相关的协议。
operation_t是一个tinyalu_pkg包中定义的枚举类型,在interface的顶部引入
tinyalu_pkg包。
在这个例子中, task将指令和数据放在op总线和操作数总线上,之后拉高start 信号并根据指令的要求对其进行拉低。
interface tinyalu_bfm;
import tinyalu_pkg::*;
byteunsigned A;
byteunsigned B;
bit clk;
bit reset_n;
wire [2:0] op;
bit start;
wire done;
wire [15:0] result;
operation_t op_set;
assign op = op_set;
initialbegin
clk = 0;
foreverbegin
#10;
clk = ~clk;
end
end
task reset_alu();
reset_n = 1'b0;
@(negedge clk);
@(negedge clk);
reset_n = 1'b1;
start = 1'b0;
endtask : reset_alu
task send_op(inputbyte iA, inputbyte iB, input operation_t iop, outputshortint alu_result);
op_set = iop;
if (iop == rst_op) begin
@(posedge clk);
reset_n = 1'b0;
start = 1'b0;
@(posedge clk);
#1;
reset_n = 1'b1;
endelsebegin
@(negedge clk);
A = iA;
B = iB;
start = 1'b1;
if (iop == no_op) begin
@(posedge clk);
#1;
start = 1'b0;
endelsebegin
do
@(negedge clk);
while (done == 0);
start = 1'b0;
end
end// else: !if(iop == rst_op)
endtask : send_op
endinterface : tinyalu_bfm
对这个行为的封装有两个好处:
-
不必再分散代码来处理协议级行为,调用这个 task 的代码比直接操作这些信号的代码要简单。
-
可在一处修改所有协议级行为,一处修改可以传递到整个design。
通过BFM的创建,实现了我们三个验证平台操作(测试, 对比和覆盖)并将其连接至 DUT:
top
module top;
tinyalu_bfm bfm();
tester tester_i (bfm);
coverage coverage_i (bfm);
scoreboard scoreboard_i(bfm);
tinyalu DUT (.A(bfm.A), .B(bfm.B), .op(bfm.op),
.clk(bfm.clk), .reset_n(bfm.reset_n),
.start(bfm.start), .done(bfm.done), .result(bfm.result));
endmodule : top
验证平台包括 4 个部分:
-
tinyalu_bfm 一个包含所有信号级行为的SystemVerilog interface
-
tester 一个生成测试激励的模块
-
coverage 一个提供功能覆盖的模块
-
scoreboard 一个测试结果的模块
在模块例化的时候,我们将interface传入,验证平台只有在DUT里有信号级的连接。我们可以看到DUT的例化引用了BFM内部的信号。
scoreboard
module scoreboard(tinyalu_bfm bfm);
import tinyalu_pkg::*;
always @(posedge bfm.done) begin
shortint predicted_result;
#1;
case (bfm.op_set)
add_op: predicted_result = bfm.A + bfm.B;
and_op: predicted_result = bfm.A & bfm.B;
xor_op: predicted_result = bfm.A ^ bfm.B;
mul_op: predicted_result = bfm.A * bfm.B;
endcase// case (op_set)
if ((bfm.op_set != no_op) && (bfm.op_set != rst_op))
if (predicted_result != bfm.result)
$error ("FAILED: A: %0h B: %0h op: %s result: %0h",
bfm.A, bfm.B, bfm.op_set.name(), bfm.result);
end
endmodule : scoreboard
通过用bfm的层次化的引用来访问BFM中的信号,剩余的逻辑跟之前验证平台
中的loop是一样的。
tester
module tester(tinyalu_bfm bfm);
import tinyalu_pkg::*;
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
functionbyte get_data();
bit [1:0] zero_ones;
zero_ones = $random;
if (zero_ones == 2'b00)
return8'h00;
elseif (zero_ones == 2'b11)
return8'hFF;
else
return$random;
endfunction : get_data
initialbegin
byteunsigned iA;
byteunsigned iB;
operation_t op_set;
shortint result;
bfm.reset_alu();
repeat (1000) begin : random_loop
op_set = get_op();
iA = get_data();
iB = get_data();
bfm.send_op(iA, iB, op_set, result);
end : random_loop
$stop;
end// initial begin
endmodule : tester
tester 模块现在更简单了,因为它不再需要处理信号级的协议了,而只需调用BFM中的task。
总结
在本章中, 将验证平台拆分成便于管理的module。这个验证平台现在有4个编译单元:tinyalu_bfm interface 、tester、scoreboard 和 coverage module。在后续章节,将用OOP来实现这个基本的四部分架构。