tinyalu_pkg.sv
package tinyalu_pkg; import uvm_pkg::*; `include "uvm_macros.svh" typedef enum bit[2:0] {no_op = 3'b000, add_op = 3'b001, and_op = 3'b010, xor_op = 3'b011, mul_op = 3'b100, rst_op = 3'b111} operation_t; virtual tinyalu_bfm bfm_g; `include "coverage.svh" `include "random_tester.svh" `include "add_tester.svh" `include "scoreboard.svh" `include "random_test.svh" `include "add_test.svh" endpackage : tinyalu_pkg
tinyalu_bfm.sv
interface tinyalu_bfm; import tinyalu_pkg::*; 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; assign op = op_set; task reset_alu(); reset_n = 1'b0; @(negedge clk); @(negedge clk); reset_n = 1'b1; start = 1'b0; endtask : reset_alu task send_op(input byte iA, input byte iB, input operation_t iop, shortint alu_result); if (iop == rst_op) begin @(negedge clk); op_set = iop; @(posedge clk); reset_n = 1'b0; start = 1'b0; @(posedge clk); #1; reset_n = 1'b1; end else begin @(negedge clk); op_set = iop; A = iA; B = iB; start = 1'b1; if (iop == no_op) begin @(posedge clk); #1; start = 1'b0; end else begin do @(negedge clk); while (done == 0); alu_result = result; start = 1'b0; end end // else: !if(iop == rst_op) endtask : send_op initial begin clk = 0; forever begin #10; clk = ~clk; end end endinterface : tinyalu_bfm
tb_classes
coverage.svh(和面向对象的验证平台那一节一样)
class coverage; virtual tinyalu_bfm bfm; byte unsigned A; byte unsigned B; operation_t op_set; covergroup op_cov; coverpoint op_set { bins single_cycle[] = {[add_op : xor_op], rst_op,no_op}; bins multi_cycle = {mul_op}; bins opn_rst[] = ([add_op:no_op] => rst_op); bins rst_opn[] = (rst_op => [add_op:no_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:no_op] [* 2]); bins manymult = (mul_op [* 3:5]); } endgroup covergroup zeros_or_ones_on_ops; all_ops : coverpoint op_set { ignore_bins null_ops = {rst_op, no_op};} a_leg: coverpoint A { bins zeros = {'h00}; bins others= {['h01:'hFE]}; bins ones = {'hFF}; } b_leg: coverpoint B { bins zeros = {'h00}; bins others= {['h01:'hFE]}; bins ones = {'hFF}; } 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 function new (virtual tinyalu_bfm b); op_cov = new(); zeros_or_ones_on_ops = new(); bfm = b; endfunction : new task execute(); forever begin : sampling_block @(negedge bfm.clk); A = bfm.A; B = bfm.B; op_set = bfm.op_set; op_cov.sample(); zeros_or_ones_on_ops.sample(); end : sampling_block endtask : execute endclass : coverage
scoreboard.svh(和面向对象的验证平台那一节一样)
class scoreboard; virtual tinyalu_bfm bfm; function new (virtual tinyalu_bfm b); bfm = b; endfunction : new task execute(); shortint predicted_result; forever begin : self_checker @(posedge bfm.done) 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 : self_checker endtask : execute endclass : scoreboard
add_tester.svh
class add_tester extends random_tester; function new (virtual tinyalu_bfm b); super.new(b); endfunction : new function operation_t get_op(); bit [2:0] op_choice; return add_op; endfunction : get_op endclass : add_tester
ramdom_tester.svh
class random_tester; virtual tinyalu_bfm bfm; function new (virtual tinyalu_bfm b); bfm = b; endfunction : new protected 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 protected 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 task execute(); byte unsigned iA; byte unsigned 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 #500; endtask : execute endclass : random_tester
定义并注册UVM test
random_test.svh
首先,random_test继承了uvm_test类。这个uvm_test是uvm_component类的子类,所以random_test也是uvm_component的子类
从uvm_component继承的子类的new()方法要遵从限制条件
- 构造器必须按顺序定义name和parent两个参数,name 在前,parent在后
- name参数必须是字符串。parent参数必须是uvm_component类型
- 构造器的第一个可执行必须调用super.new(name, parent),然后UVM才能正常工作
class random_test extends uvm_test; `uvm_component_utils(random_test); //我们在 class 表达式之后就使用了这个宏,这个宏在 Factory 里注册了random_test 类。现在 Factory 可以创建 random_test 对象了 virtual tinyalu_bfm bfm; function new (string name, uvm_component parent); super.new(name,parent); if(!uvm_config_db #(virtual tinyalu_bfm)::get(null, "*","bfm", bfm)) //我们调用uvm_config_db的get()方法来取得bfm的句柄。注意我们传入了set()方法中用过的bfm字符串和BFM句柄变量。 $fatal("Failed to get BFM"); endfunction : new task run_phase(uvm_phase phase); random_tester random_tester_h; coverage coverage_h; scoreboard scoreboard_h; phase.raise_objection(this); //提出objection random_tester_h = new(bfm); coverage_h = new(bfm); scoreboard_h = new(bfm); fork coverage_h.execute(); scoreboard_h.execute(); join_none random_tester_h.execute(); phase.drop_objection(this); //撤销objection endtask : run_phase endclass
定义类还需要做最后一件事情,注册到 Factory。回顾我们的动物 Factory 的话你会发现我们是用 Hardcoding 编写出来的 , Factory 能处理的动物种类。要增加动物到 Factory 的话只能改Factory 的源码
UVM开发者不希望我们改他们的代码,于是他们创建了用 uvm_component_utils 宏的机制来解决这个问题。
我们在 class 表达式之后就使用了这个宏,这个宏在 Factory 里注册了random_test 类。现在 Factory 可以创建 random_test 对象了
我们在顶层module调用 run_test(),UVM 用 Factory 创建并通过调用run_phase() 方法来启动 Test 对象。UVM 的 uvm_test 定义里有 run_phase() 方法,UVM 用run_phase() 来执行我们的 Test
我们的Test 要工作的话必须重载 run_phase()方法。这个方法名必须是 run_phase() 然后必须有一个 uvm_phase 类型的phase参数。
add_test.svh
class add_test extends uvm_test; //add_test继承了uvm_test类
`uvm_component_utils(add_test); //将add_test注册到Factory
virtual tinyalu_bfm bfm;
function new (string name, uvm_component parent);
super.new(name,parent);
if(!uvm_config_db #(virtual tinyalu_bfm)::get(null, "*","bfm", bfm))
$fatal("Failed to get BFM");
endfunction : new
task run_phase(uvm_phase phase);
add_tester add_tester_h;
coverage coverage_h;
scoreboard scoreboard_h;
phase.raise_objection(this);
add_tester_h = new(bfm);
coverage_h = new(bfm);
scoreboard_h = new(bfm);
fork
coverage_h.execute();
scoreboard_h.execute();
join_none
add_tester_h.execute();
phase.drop_objection(this);
endtask : run_phase
endclass
用UVM启动仿真
top.sv
module top; import uvm_pkg::*; `include "uvm_macros.svh" import tinyalu_pkg::*; `include "tinyalu_macros.svh" tinyalu_bfm 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)); initial begin uvm_config_db #(virtual tinyalu_bfm)::set(null, "*", "bfm", bfm); run_test(); //run_test()方法从命令行得到一个类名字符串,然后用UVM Factory来创建这个类的测试对象,然后这个测试对象开始运行测试 end endmodule : top
基础知识
uvm_config_db
第一部分:uvm_config_db的使用方法
uvm_config_db作用是把验证环境的一些资源配置为类似全局变量一样,使得它对于整个验证环境来说是可见的。最常见的是在验证环境顶层通过它对一些组件进行配置,组件可以在建立的阶段读取这些配置实现组件不同工作模式的切换,下面是使用uvm_config_db的语法:
uvm_config_db#(T)::set(uvm_component cntxt, string inst_name, string field_name, T value);
uvm_config_db#(T)::get(uvm_component cntxt, string inst_name, string field_name, ref T value);
例子:
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if)
uvm_config_db#(virtual my_if)::get(this, "", "vif", vif)
在以上两个函数set和get是使用它时要调用的函数,set表示把要处理的资源放进全局可见的数据库,get表示从全局可见的数据库输出需要的资源,使用set和get函数时有五个参数需要制定,第一个是uvm_config_db类的参数#(T),T表示要set或get的资源的类型,它可以是虚拟接口,sequencer等等,第二个cntxt和第三个参数inst_name一起定义了uvm_config_db中set或get函数的作用范围。第四个参数决定了是对作用范围中的哪个对象或变量进行操作,第五个参数value会存储当前操作对象的句柄或者操作变量的值。需要注意的是,当制定第二个参数是一个uvm组件时,uvm会用它的全局名字取替换它,而全局名字会通过uvm的get_full_name来获取。
第二部分:uvm_config_db的作用对象
在uvm验证环境中并不是所有的资源我们都会用uvm_config_db去配置,这样会让我们的验证环境变得不可维护,下面是一些典型的在验证环境中使用uvm_config_db去配置的资源:
-
虚拟接口,虚拟接口提供了一个访问真实虚接口的入口,我们会把虚拟接口的句柄放到全局的数据库中,uvm中的这些组件会通过get函数拿到虚拟接口的句柄对接口数据进行操作。
-
配置数据,负责配置环境的类中包含许多可以控制验证环境的变量,它会改变这些变量,并且通过set函数把它放到我们全局的数据库中,其他的组件通过get函数来取到这些变量的值,再根据这些值去改变工作模式
-
sequencers,在uvm中sequencers负责把我们写的sequence进行排队送到driver上去,所以sequence需要有对sequencer的访问入口,我们同样通过uvm_config_db的方式来把sequencer的句柄传给sequence
run_test()
run_test() task从仿真器的命令行+UVM_TESTNAME里读出参数,然后对这个类名例化对象