UVM基础总结——基于《UVM实战》示例

UVM基础总结——基于《UVM实战》示例

一、前言

工作一直在做SoC验证,更关注模块间的连接性和匹配性,所以相比于擅长随机约束激励的UVM来说,定向测试的概念更容易debug。当然前提是IP已经被充分验证。因此觉得接触UVM的机会较少。到现在发现即使在SoC验证中依然有它的用武之地。比如验证可独立于CPU工作的IP、快速对系统性能进行评估、重用IP级别的验证环境,甚至是一些通用的VIP也有基于UVM编写的。基于这些考量,也逐渐开始接触。《UVM实战》是很多验证工程师的启蒙,本文借用书中开头的示例简单梳理下UVM的基本知识。

二、UVM基础概述

关于UVM的知识网络上已经铺天盖地了,下边的内容只是自己的一些认识和随记。UVM其实就是基于SV语言写的用于验证的代码库和对应的验证规范。下图是UVM验证环境的整体结构(图来源见参考文献1)。图中注释介绍了每个组成部分的作用。《UVM实战》书中给我留下最深刻的印象就是用子弹、弹夹和手枪分别类比transaction sequence和sequencer,这也是UVM环境灵活的重要原因之一。这是激励的产生机制,至于响应的采集和响应任务会交给monitor和scoreboard。后者中的期望数据或者参考数据的来源比较灵活,函数、文件或是reference model的响应均可。

以上是UVM的空间维度,这一概念也被抽象成如下的树状结构。各个部分必然存在信息交互。sequence和sequencer之间传递的是transaction,实际上component之间也是transaction级别的通信,叫做TLM (transaction level model)最常见的就是monitor中uvm_analysis_port通过uvm_tlm_analysis_fifo连接到reference model或scoreboard中的uvm_blocking_get_port。这样可以确保transaction能够传递给目的组件。

另一方面UVM在时间维度上也做了规范。phase机制明确划分了各个阶段所要完成的任务。其中比较重要的是run phase这一消耗仿真时间的task phase以及ojbection的概念。只有每个phase中所有raise的objection都被drop后才会执行下一个phase的任务。搭建空间与时间维度桥梁的也是Phase--build phase是在树状结构中自上而下执行,其他不消耗仿真时间的phase都是自下而上运行。run phase则自上而下启动同事运行。所有phase的顺序见图3(图来源见参考文献2)

弄明白这三张图,也就是组件、组件间通信和phase机制,基本上可以看懂别人写的代码了。

三、验证环境示例

各个UVM object和component:

sequence:

1 class my_sequence extends uvm_sequence #(my_transaction);

2 my_transaction m_trans;

3

4 function new(string name="my_sequence");

5 super.new(name);

6 endfunction

7

8 virtual task body();

9 if(starting_phase != null)

10 starting_phase.raise_objection(this);

11

12 repeat(10) begin

13 `uvm_do(m_trans)

14 end

15 #1000;

16

17 if(starting_phase != null)

18 starting_phase.drop_objection(this);

19 endtask

20

21 `uvm_object_utils(my_sequence)

22

23 endclass

my_sequence

transaction:

1 `ifndef MY_TRANSACTION__SV

2 `define MY_TRANSACTION__SV

3

4 class my_transaction extends uvm_sequence_item;

5

6 rand bit[47:0] dmac;

7 rand bit[47:0] smac;

8 rand bit[15:0] ether_type;

9 rand byte pload[];

10 rand bit[31:0] crc;

11

12 constraint pload_cons{

13 pload.size >= 46;

14 //pload.size <= 1500;

15 pload.size <= 200;

16 dmac == 48'h01_02_03_04_05_06;

17 smac == 48'h60_50_40_30_20_10;

18

19 }

20

21 function bit[31:0] calc_crc();

22 return 32'h0;

23 endfunction

24

25 function void post_randomize();

26 crc = calc_crc;

27 endfunction

28

29 //`uvm_object_utils(my_transaction)

30 `uvm_object_utils_begin(my_transaction)

31 `uvm_field_int(dmac,UVM_ALL_ON)

32 `uvm_field_int(smac,UVM_ALL_ON)

33 `uvm_field_int(ether_type,UVM_ALL_ON)

34 `uvm_field_array_int(pload,UVM_ALL_ON)

35 `uvm_field_int(crc,UVM_ALL_ON)

36 `uvm_object_utils_end

37

38 function new(string name = "my_transaction");

39 super.new();

40 endfunction

41

42 /*function void my_print();

43 $display("dmac = %0h", dmac);

44 $display("smac = %0h", smac);

45 $display("ether_type = %0h", ether_type);

46 for(int i = 0; i < pload.size; i++) begin

47 $display("pload[%0d] = %0h", i, pload[i]);

48 end

49 $display("crc = %0h", crc);

50 endfunction

51

52 function void my_copy(my_transaction tr);

53 if(tr == null)

54 `uvm_fatal("my_transaction", "tr is null!!!!")

55 dmac = tr.dmac;

56 smac = tr.smac;

57 ether_type = tr.ether_type;

58 pload = new[tr.pload.size()];

59 for(int i = 0; i < pload.size(); i++) begin

60 pload[i] = tr.pload[i];

61 end

62 crc = tr.crc;

63 endfunction

64

65 function bit my_compare(my_transaction tr);

66 bit result;

67

68 if(tr == null)

69 `uvm_fatal("my_transaction", "tr is null!!!!")

70 result = ((dmac == tr.dmac) &&

71 (smac == tr.smac) &&

72 (ether_type == tr.ether_type) &&

73 (crc == tr.crc));

74 if(pload.size() != tr.pload.size())

75 result = 0;

76 else

77 for(int i = 0; i < pload.size(); i++) begin

78 if(pload[i] != tr.pload[i])

79 result = 0;

80 end

81 return result;

82 endfunction*/

83

84 endclass

85 `endif

my_transaction

sequencer:

1 `ifndef MY_SEQUENCER__SV

2 `define MY_SEQUENCER__SV

3

4 class my_sequencer extends uvm_sequencer #(my_transaction);

5

6 function new(string name, uvm_component parent);

7 super.new(name, parent);

8 endfunction

9

10 `uvm_component_utils(my_sequencer)

11 endclass

12

13 `endif

my_sequencer

driver:

1 `ifndef MY_DRIVER__SV

2 `define MY_DRIVER__SV

3 class my_driver extends uvm_driver#(my_transaction);

4

5 virtual my_if vif;

6

7 `uvm_component_utils(my_driver)

8 function new(string name = "my_driver", uvm_component parent = null);

9 super.new(name, parent);

10 endfunction

11

12 virtual function void build_phase(uvm_phase phase);

13 super.build_phase(phase);

14 if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))

15 `uvm_fatal("my_driver", "virtual interface must be set for vif!!!")

16 endfunction

17

18 extern task main_phase(uvm_phase phase);

19 extern task drive_one_pkt(my_transaction tr);

20 endclass

21

22 /*task my_driver::main_phase(uvm_phase phase);

23 my_transaction tr;

24 phase.raise_objection(this);

25 vif.data <= 8'b0;

26 vif.valid <= 1'b0;

27 while(!vif.rst_n)

28 @(posedge vif.clk);

29 for(int i = 0; i < 2; i++) begin

30 req = new("req");

31 assert(req.randomize() with {pload.size == 200;

32 dmac == 48'h01_02_03_04_05_06;

33 smac == 48'h60_50_40_30_20_10;

34 }

35 );

36 drive_one_pkt(req);

37 end

38 repeat(5) @(posedge vif.clk);

39 phase.drop_objection(this);

40 endtask*/

41

42 task my_driver::main_phase(uvm_phase phase);

43 vif.data <= 8'b0;

44 vif.valid <= 1'b0;

45

46 while(!vif.rst_n)

47 @(posedge vif.clk);

48

49 while(1)begin

50 seq_item_port.get_next_item(req);

51 drive_one_pkt(req);

52 seq_item_port.item_done();

53 end

54 endtask

55

56 task my_driver::drive_one_pkt(my_transaction tr);

57

58 byte unsigned data_q[];

59 int unsigned data_size;

60

61 data_size = tr.pack_bytes(data_q)/8;//fill data_q on the order in uvm_object_utils of transaction

62 `uvm_info("my_driver","begin to drive one pkt",UVM_LOW);

63 repeat(3) @(posedge vif.clk);

64 for(int i=0;i

65 @(posedge vif.clk);

66 vif.valid <= 1'b1;

67 vif.data <= data_q[i];

68 end

69 @(posedge vif.clk);

70 vif.valid <= 1'b0;

71 repeat(10) @(posedge vif.clk);

72 `uvm_info("my_driver","end drive one pkt",UVM_LOW);

73 endtask

74

75

76 `endif

my_driver

monitor:

1 `ifndef MY_MONITOR__SV

2 `define MY_MONITOR__SV

3 class my_monitor extends uvm_monitor;

4

5 virtual my_if vif;

6

7 uvm_analysis_port #(my_transaction) ap;

8

9 `uvm_component_utils(my_monitor)

10 function new(string name = "my_monitor", uvm_component parent = null);

11 super.new(name, parent);

12 endfunction

13

14 virtual function void build_phase(uvm_phase phase);

15 super.build_phase(phase);

16 if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))

17 `uvm_fatal("my_monitor", "virtual interface must be set for vif!!!")

18 ap = new("ap", this);

19 endfunction

20

21 extern task main_phase(uvm_phase phase);

22 extern task collect_one_pkt(my_transaction tr);

23 endclass

24

25 task my_monitor::main_phase(uvm_phase phase);

26 my_transaction tr;

27 while(1) begin

28 tr = new("tr");

29 collect_one_pkt(tr);

30 ap.write(tr);

31 end

32 endtask

33

34 /*task my_monitor::collect_one_pkt(my_transaction tr);

35 bit[7:0] data_q[$];

36 int psize;

37 while(1) begin

38 @(posedge vif.clk);

39 if(vif.valid) break;

40 end

41

42 `uvm_info("my_monitor", "begin to collect one pkt", UVM_LOW);

43 while(vif.valid) begin

44 data_q.push_back(vif.data);

45 @(posedge vif.clk);

46 end

47 //pop dmac

48 for(int i = 0; i < 6; i++) begin

49 tr.dmac = {tr.dmac[39:0], data_q.pop_front()};

50 end

51 //pop smac

52 for(int i = 0; i < 6; i++) begin

53 tr.smac = {tr.smac[39:0], data_q.pop_front()};

54 end

55 //pop ether_type

56 for(int i = 0; i < 2; i++) begin

57 tr.ether_type = {tr.ether_type[7:0], data_q.pop_front()};

58 end

59

60 psize = data_q.size() - 4;

61 tr.pload = new[psize];

62 //pop payload

63 for(int i = 0; i < psize; i++) begin

64 tr.pload[i] = data_q.pop_front();

65 end

66 //pop crc

67 for(int i = 0; i < 4; i++) begin

68 tr.crc = {tr.crc[23:0], data_q.pop_front()};

69 end

70 `uvm_info("my_monitor", "end collect one pkt", UVM_LOW);

71 endtask*/

72

73 task my_monitor::collect_one_pkt(my_transaction tr);

74 byte unsigned data_q[$];

75 byte unsigned data_array[];

76

77 logic [8-1:0] data;

78 logic valid=0;

79 int data_size;

80

81 while(1) begin

82 @(posedge vif.clk);

83 if(vif.valid) break;

84 end

85

86

87 `uvm_info("my_monitor","begin to collect one pkt",UVM_LOW);

88 while(vif.valid)begin

89 data_q.push_back(vif.data);

90 @(posedge vif.clk);

91 end

92

93 data_size = data_q.size();

94 data_array = new[data_size];

95 for(int i=0;i

96 data_array[i] = data_q[i];

97 end

98

99 tr.pload = new[data_size-18];

100 data_size = tr.unpack_bytes(data_array)/8;

101 `uvm_info("my_monitor","end collect one pkt",UVM_LOW);

102 endtask

103

104

105 `endif

my_monitor

agent:

1 `ifndef MY_AGENT__SV

2 `define MY_AGENT__SV

3

4 class my_agent extends uvm_agent ;

5 my_sequencer sqr;

6 my_driver drv;

7 my_monitor mon;

8

9 uvm_analysis_port #(my_transaction) ap;

10

11 function new(string name, uvm_component parent);

12 super.new(name, parent);

13 endfunction

14

15 extern virtual function void build_phase(uvm_phase phase);

16 extern virtual function void connect_phase(uvm_phase phase);

17

18 `uvm_component_utils(my_agent)

19 endclass

20

21

22 function void my_agent::build_phase(uvm_phase phase);

23 super.build_phase(phase);

24 if (is_active == UVM_ACTIVE) begin

25 sqr = my_sequencer::type_id::create("sqr",this);

26 drv = my_driver::type_id::create("drv", this);

27 end

28 mon = my_monitor::type_id::create("mon", this);

29 endfunction

30

31 function void my_agent::connect_phase(uvm_phase phase);

32 super.connect_phase(phase);

33 if(is_active == UVM_ACTIVE)begin

34 drv.seq_item_port.connect(sqr.seq_item_export);

35 end

36 ap = mon.ap;

37 endfunction

38

39 `endif

my_agent

reference model:

1 `ifndef MY_MODEL__SV

2 `define MY_MODEL__SV

3

4 class my_model extends uvm_component;

5

6 uvm_blocking_get_port #(my_transaction) port;

7 uvm_analysis_port #(my_transaction) ap;

8

9 extern function new(string name, uvm_component parent);

10 extern function void build_phase(uvm_phase phase);

11 extern virtual task main_phase(uvm_phase phase);

12

13 `uvm_component_utils(my_model)

14 endclass

15

16 function my_model::new(string name, uvm_component parent);

17 super.new(name, parent);

18 endfunction

19

20 function void my_model::build_phase(uvm_phase phase);

21 super.build_phase(phase);

22 port = new("port", this);

23 ap = new("ap", this);

24 endfunction

25

26 task my_model::main_phase(uvm_phase phase);

27 my_transaction tr;

28 my_transaction new_tr;

29 super.main_phase(phase);

30 while(1) begin

31 port.get(tr);

32 new_tr = new("new_tr");

33 //new_tr.my_copy(tr);

34 new_tr.copy(tr);

35 `uvm_info("my_model", "get one transaction, copy and print it:", UVM_LOW)

36 //new_tr.my_print();

37 new_tr.print();

38 ap.write(new_tr);

39 end

40 endtask

41 `endif

my_model

scoreboard:

1 `ifndef MY_SCOREBOARD__SV

2 `define MY_SCOREBOARD__SV

3 class my_scoreboard extends uvm_scoreboard;

4 my_transaction expect_queue[$];

5 uvm_blocking_get_port #(my_transaction) exp_port;

6 uvm_blocking_get_port #(my_transaction) act_port;

7 `uvm_component_utils(my_scoreboard)

8

9 extern function new(string name, uvm_component parent = null);

10 extern virtual function void build_phase(uvm_phase phase);

11 extern virtual task main_phase(uvm_phase phase);

12 endclass

13

14 function my_scoreboard::new(string name, uvm_component parent = null);

15 super.new(name, parent);

16 endfunction

17

18 function void my_scoreboard::build_phase(uvm_phase phase);

19 super.build_phase(phase);

20 exp_port = new("exp_port", this);

21 act_port = new("act_port", this);

22 endfunction

23

24 task my_scoreboard::main_phase(uvm_phase phase);

25 my_transaction get_expect, get_actual, tmp_tran;

26 bit result;

27

28 super.main_phase(phase);

29 fork

30 while (1) begin

31 exp_port.get(get_expect);

32 expect_queue.push_back(get_expect);

33 end

34 while (1) begin

35 act_port.get(get_actual);

36 if(expect_queue.size() > 0) begin

37 tmp_tran = expect_queue.pop_front();

38 //result = get_actual.my_compare(tmp_tran);

39 result = get_actual.compare(tmp_tran);

40 if(result) begin

41 `uvm_info("my_scoreboard", "Compare SUCCESSFULLY", UVM_LOW);

42 end

43 else begin

44 `uvm_error("my_scoreboard", "Compare FAILED");

45 $display("the expect pkt is");

46 //tmp_tran.my_print();

47 tmp_tran.print();

48 $display("the actual pkt is");

49 //get_actual.my_print();

50 get_actual.print();

51 end

52 end

53 else begin

54 `uvm_error("my_scoreboard", "Received from DUT, while Expect Queue is empty");

55 $display("the unexpected pkt is");

56 //get_actual.my_print();

57 get_actual.print();

58 end

59 end

60 join

61 endtask

62 `endif

my_scoreboard

base test:

1 `ifndef BASE_TEST__SV

2 `define BASE_TEST__SV

3

4 class base_test extends uvm_test;

5

6 my_env env;

7

8 function new(string name = "base_test", uvm_component parent = null);

9 super.new(name,parent);

10 endfunction

11

12 extern virtual function void build_phase(uvm_phase phase);

13 extern virtual function void report_phase(uvm_phase phase);

14 `uvm_component_utils(base_test)

15 endclass

16

17

18 function void base_test::build_phase(uvm_phase phase);

19 super.build_phase(phase);

20 env = my_env::type_id::create("env", this);

21 //uvm_config_db#(uvm_object_wrapper)::set(this,

22 //"env.i_agt.sqr.main_phase",

23 //"default_sequence",

24 // my_sequence::type_id::get());

25 endfunction

26

27 function void base_test::report_phase(uvm_phase phase);

28 uvm_report_server server;

29 int err_num;

30 super.report_phase(phase);

31

32 server = get_report_server();

33 err_num = server.get_severity_count(UVM_ERROR);

34

35 if (err_num != 0) begin

36 $display("TEST CASE FAILED");

37 end

38 else begin

39 $display("TEST CASE PASSED");

40 end

41 endfunction

42

43 `endif

base_test

Interface:

1 `ifndef MY_IF__SV

2 `define MY_IF__SV

3

4 interface my_if(input clk, input rst_n);

5

6 logic [7:0] data;

7 logic valid;

8 endinterface

9

10 `endif

my_if

testbench top:

1 `timescale 1ns/1ps

2 `include "uvm_macros.svh"

3

4 import uvm_pkg::*;

5 `include "my_if.sv"

6 `include "my_transaction.sv"

7 //`include "my_sequence.sv"

8 `include "my_driver.sv"

9 `include "my_monitor.sv"

10 `include "my_sequencer.sv"

11 `include "my_agent.sv"

12 `include "my_model.sv"

13 `include "my_scoreboard.sv"

14 `include "my_env.sv"

15 `include "base_test.sv"

16 `include "my_case0.sv"

17 `include "my_case1.sv"

18

19 module top_tb;

20

21 reg clk;

22 reg rst_n;

23 reg[7:0] rxd;

24 reg rx_dv;

25 wire[7:0] txd;

26 wire tx_en;

27

28 my_if input_if(clk, rst_n);

29 my_if output_if(clk, rst_n);

30

31 dut my_dut(.clk(clk),

32 .rst_n(rst_n),

33 .rxd(input_if.data),

34 .rx_dv(input_if.valid),

35 .txd(output_if.data),

36 .tx_en(output_if.valid));

37

38 initial begin

39 clk = 0;

40 forever begin

41 #100 clk = ~clk;

42 end

43 end

44

45 initial begin

46 rst_n = 1'b0;

47 #1000;

48 rst_n = 1'b1;

49 end

50

51 initial begin

52 //run_test("my_env");

53 run_test();

54 end

55

56 initial begin

57 uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.drv", "vif", input_if);

58 uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.mon", "vif", input_if);

59 uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.o_agt.mon", "vif", output_if);

60 end

61

62 //Enable dump waveform

63 initial

64 begin

65 $shm_open("test.shm");

66 $shm_probe(top_tb,"ACTFM");

67 #1ms;

68 $shm_close;

69 end

70

71 endmodule

top_tb

需要注意是在定义class前,如果这个class会使用到其他class,最好在前面加type class 。例如在class my_sequence extends uvm_sequence前一行加上type class my_transaction。否则如果my_sequence在my_transaction之前编译,就会报错。虽然可以通过在testbench top中先include my_transaction.sv解决,但是大大降低了代码的重用性。

四、测试用例及仿真

1 `ifndef MY_CASE0__SV

2 `define MY_CASE0__SV

3 class case0_sequence extends uvm_sequence #(my_transaction);

4 my_transaction m_trans;

5

6 function new(string name= "case0_sequence");

7 super.new(name);

8 endfunction

9

10 virtual task body();

11 if(starting_phase != null)

12 starting_phase.raise_objection(this);

13 repeat (10) begin

14 `uvm_do(m_trans)

15 end

16 #100;

17 if(starting_phase != null)

18 starting_phase.drop_objection(this);

19 endtask

20

21 `uvm_object_utils(case0_sequence)

22 endclass

23

24

25 class my_case0 extends base_test;

26

27 function new(string name = "my_case0", uvm_component parent = null);

28 super.new(name,parent);

29 endfunction

30 extern virtual function void build_phase(uvm_phase phase);

31 `uvm_component_utils(my_case0)

32 endclass

33

34

35 function void my_case0::build_phase(uvm_phase phase);

36 super.build_phase(phase);

37

38 uvm_config_db#(uvm_object_wrapper)::set(this,

39 "env.i_agt.sqr.main_phase",

40 "default_sequence",

41 case0_sequence::type_id::get());

42 endfunction

43

44 `endif

my_case0

所用的测试用例都扩展自自定义的base_test,后者又来自uvm_test。base_test例化整个UVM environment,用例中主要要做的事情就是启动sequence, 包括调用start任务手动启动和自动启动方式,具体见参考文献3.这里是最常见的自动启动方式:用uvm_config_db将要启动的sequence设置为sequencer main_phase的default_sequence.

每个sequence中都有个叫body的task,当sequence被启动时会自动调用这个task。通过`uvm_do宏来产生transaction。更灵活的方式是先后使用`uvm_create()和`uvm_send()实现这一功能,并在两者间控制transaction的各个field。只有当消耗仿真时间的driver调用了item_done()后一次transaction的发送才算结束。

五、总结

我们不创造知识,我们只是知识的搬运工。将知识灵活运用,创造出合理高效可重用的VIP,验证环境乃至整个验证流程方法是IC验证的核心技能,这些技能都是为尽可能快速发现潜在问题这一核心任务做的准备。

六、参考文献

1 UVM——基础类结构图(uvm树、常用继承关系结构)https://blog.csdn.net/weixin_46022434/article/details/105838815

2 UVM phase机制 https://blog.csdn.net/qq_41394155/article/details/81914826

3 UVM中启动sequence的方法 https://aijishu.com/a/1060000000132327

相关

移动服务密码怎么查
365直播网APP下载

移动服务密码怎么查

📅 07-03 👁️ 7409
美图秀秀防盗图水印?
beat365英超欧冠平台

美图秀秀防盗图水印?

📅 07-03 👁️ 3344
2345浏览器无法卸载怎么办?卸载2345浏览器最彻底的方法