Post

UVM-demo

overview

整个项目结构如下:

pj_archi

有一些空格亮度问题,不一一解决了 code见代码地址

interface

定义接口,并针对不同的模块确定方向

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
`ifndef GCD_IF__SV
`define GCD_IF__SV

interface gcd_if(input clk);
    logic [31:0]  opa;
    logic [31:0]  opb;
    logic         start;
    logic         reset;
    logic [31:0]  result;
    logic         done;

  clocking drv_cb @(posedge clk);
    output  opa;
    output  opb;
    output  start;
    output  reset;
    input   result;
    input   done;
  endclocking
    
  clocking mon_cb @(posedge clk);
    input   opa;
    input   opb;
    input   start;
    input   reset;
    input   result;
    input   done;
  endclocking
endinterface //gcd_if
`endif

定义uvm_sequence_item,即传输的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
`ifndef GCD_TR__SV
`define GCD_TR__SV

class gcd_tr extends uvm_sequence_item;
    rand bit[31:0] opa;
    rand bit[31:0] opb;
    rand bit[31:0] result;

    `uvm_object_utils_begin(gcd_tr)
        `uvm_field_int(opa, UVM_ALL_ON | UVM_NOPACK);
        `uvm_field_int(opb, UVM_ALL_ON | UVM_NOPACK);
        `uvm_field_int(result, UVM_ALL_ON | UVM_NOPACK);
    `uvm_object_utils_end
    function new(string name = "gcd_tr");
        super.new(name);
    endfunction: new

    function void post_randomize();
        super.post_randomize();
    endfunction: post_randomize
endclass: gcd_tr
`endif

uvm_field_int在类中注册 int 类型的成员变量,UVM_ALL_ON启用所有自动化方法对该变量的处理,包括 copycompareprintpackunpack 等。UVM_NOPACK排除 packunpack 方法对该变量的处理

这里只关心传输的类型 有这三个数据要在组件之间传输

driver

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
`ifndef GCD_DRIVER__SV
`define GCD_DRIVER__SV

class gcd_driver extends uvm_driver#(gcd_tr);//gcd_tr表示transaction的类型
    virtual gcd_if sigs;
    int send_num;//记录传输的数量
    `uvm_component_utils(gcd_driver)
    
    function new(string name = "gcd_driver", uvm_component parent = null);
        super.new(name, parent);
    endfunction: new

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        if(!uvm_config_db#(virtual gcd_if)::get(this, "", "drv_if", sigs))
            `uvm_fatal("GCD/DRIVER/BUILD", "Driver cannot get the interface");
        `uvm_info("GCD/DRIVER/BUILD", "Driver has been built", UVM_LOW);
    endfunction: build_phase
    
    extern task main_phase(uvm_phase phase);
    extern task reset_dut();
    extern task send_tr(gcd_tr tr);
    extern function void check_phase(uvm_phase phase);
endclass: gcd_driver

task gcd_driver::main_phase(uvm_phase phase);
    gcd_tr tr;
    reset_dut();
    send_num = 0;
    while(1) begin
        seq_item_port.get_next_item(tr);
        send_tr(tr);
        seq_item_port.item_done();
        send_num++;
    end
endtask: main_phase

task gcd_driver::reset_dut();
    @(sigs.drv_cb);
    sigs.reset = 1;
    sigs.start = 0;
    sigs.opa = 0;
    sigs.opb = 0;
    @(sigs.drv_cb);
    sigs.reset = 0;
endtask: reset_dut

task gcd_driver::send_tr(gcd_tr tr);
    @(sigs.drv_cb);
    sigs.start = 1;
    sigs.opa = tr.opa;
    sigs.opb = tr.opb;
    @(sigs.drv_cb);
    sigs.start = 0;
    while(!sigs.done)
        @(sigs.drv_cb);
    repeat(10)
        @(sigs.drv_cb);
endtask: send_tr

function void gcd_driver::check_phase(uvm_phase phase);
    super.check_phase(phase);
    `uvm_info("GCD/DRIVE/POST", $psprintf("%0d item(s) have(has) been sent", send_num), UVM_LOW);
endfunction
`endif

uvm_info("GCD/DRIVER/BUILD", "Driver has been built", UVM_LOW);

  • 第一个参数 "GCD/DRIVER/BUILD":消息的标识符或标签,方便分类和过滤。
  • 第三个参数 UVM_LOW:消息的冗余级别
    • UVM_NONE (0):无冗余,最高优先级,始终显示。
    • UVM_LOW (100):低冗余级别。
    • UVM_MEDIUM (200):中等冗余级别。
    • UVM_HIGH (300):高冗余级别。
    • UVM_FULL (400):完整冗余级别。
    • UVM_DEBUG (500):调试级别,最详细的信息。

if (!uvm_config_db#(virtual gcd_if)::get(this, "", "drv_if", sigs)) 从 UVM 配置数据库中获取名为 drv_if 的interface gcd_if,并将其赋值给驱动器内部的变量 sigs。如果获取失败,则报告一个致命错误,终止仿真。

uvm_config_db:UVM 提供的配置数据库类,用于在组件之间传递配置信息。

driver主要完成复位,随机数据的生成,等待计算结束

monitor

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
`ifndef GCD_MONITOR__SV
`define GCD_MONITOR__SV

class gcd_monitor extends uvm_monitor;
    int mon_num;
    virtual gcd_if sigs;
    uvm_analysis_port #(gcd_tr) mon_ap;
    `uvm_component_utils(gcd_monitor)
    
    function new(string name = "gcd_monitor", uvm_component parent = null);
        super.new(name, parent);
    endfunction
    
    virtual function void build_phase(uvm_phase phase);
        if(!uvm_config_db#(virtual gcd_if)::get(this, "", "mon_if", sigs))
            `uvm_fatal("GCD/MONITOR/BUILD", "Monitor cannot get interface");
        mon_ap = new("mon_ap", this);
        `uvm_info("GCD/MON/BUILD", "Monitor has been built", UVM_LOW);
    endfunction: build_phase
  
    extern task main_phase(uvm_phase phase);
    extern task collect_tr(gcd_tr tr);
    extern function void check_phase(uvm_phase phase);
endclass: gcd_monitor

task gcd_monitor::main_phase(uvm_phase phase);
    gcd_tr tr;
    mon_num = 0;
    while(1) begin
        tr = new("tr");//新的tr,要和旧的做比较
        collect_tr(tr);
        mon_ap.write(tr);
        mon_num++;
    end
endtask: main_phase

task gcd_monitor::collect_tr(gcd_tr tr);
    while(1) begin
        @(sigs.mon_cb);
        if(sigs.start)
            break;
    end

    `uvm_info("GCD/MON/COLLECT", "Get the input data", UVM_LOW);
    tr.opa = sigs.opa;
    tr.opb = sigs.opb;
    while(1) begin
        @(sigs.mon_cb);
        if(sigs.done)
            break;
    end
    tr.result = sigs.result;
    `uvm_info("GCD/MON/COLLECT", "Get the result data", UVM_LOW);
endtask: collect_tr

function void gcd_monitor::check_phase(uvm_phase phase);
    super.check_phase(phase);
    `uvm_info("GCD/MON/CHECK", $psprintf("%0d item(s) have(has) been monitored", mon_num), UVM_LOW);
endfunction: check_phase
`endif

sequencer

1
2
3
4
5
6
7
8
9
`ifndef GCD_SEQUENCER__SV
`define GCD_SEQUENCER__SV
class gcd_sequencer extends uvm_sequencer #(gcd_tr);
    function new(string name = "gcd_sequencer", uvm_component parent = null);
        super.new(name, parent);
    endfunction: new 
    `uvm_component_utils(gcd_sequencer)
endclass: gcd_sequencer
`endif

没什么可说的,套模板

agent

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
class gcd_agent extends uvm_agent;
    gcd_driver drv;
    gcd_monitor mon;
    gcd_sequencer sqr;
    uvm_analysis_port #(gcd_tr) ap;//ap在代理(agent)内部。而mon_ap在monitor内部
    
    function new(string name = "gcd_agent", uvm_component parent = null);//组件的父级,`null`表示顶级组件
        super.new(name, parent);
    endfunction: new
    
    extern virtual function void build_phase(uvm_phase phase);//外部函数,在class外提供
    extern virtual function void connect_phase(uvm_phase phase);//virtual:允许子类重写此方法。
    `uvm_component_utils(gcd_agent)
endclass: gcd_agent

function void gcd_agent::build_phase(uvm_phase phase);
    super.build_phase(phase);
    if(is_active == UVM_ACTIVE) begin
        sqr = gcd_sequencer::type_id::create("sqr", this);
        drv = gcd_driver::type_id::create("drv", this);
    end
    mon = gcd_monitor::type_id::create("mon", this);
endfunction: build_phase
  
function void gcd_agent::connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    if(is_active == UVM_ACTIVE) begin
        drv.seq_item_port.connect(sqr.seq_item_export);
    end
    ap = mon.mon_ap;
endfunction: connect_phase
`endif

agent是用于封装驱动器(driver)、监视器(monitor)和序列器(sequencer)组件的顶层模块。

uvm_analysis_port用于发布(publish)事务的分析端口,指定分析端口传输的数据类型为 gcd_tr

利用 if(is_active == UVM_ACTIVE)来去除部分component

连接驱动器的 seq_item_port 与序列器的 seq_item_export,以便驱动器能够从序列器获取事务。

将monitor的分析端口连接到agent的分析端口,使得monitor捕获的事务可以传递给agent的外部。


上述完成了基本的UV Component

在此基础上进一步的有:i_agt、o_agt、re_model、scb

uvm


env封装

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
`ifndef GCD_ENV__SV
`define GCD_ENV__SV

class gcd_env extends uvm_env;
    gcd_agent i_agt;
    gcd_agent o_agt;
    gcd_re_model mdl;
    gcd_scb scb;
    
    uvm_tlm_analysis_fifo #(gcd_tr) agt_scb_fifo;
    uvm_tlm_analysis_fifo #(gcd_tr) agt_mdl_fifo;
    uvm_tlm_analysis_fifo #(gcd_tr) mdl_scb_fifo;

    function new(string name = "gcd_env", uvm_component parent = null);
        super.new(name, parent);
    endfunction: new
    extern virtual function void build_phase(uvm_phase phase);
    extern virtual function void connect_phase(uvm_phase phase);
    `uvm_component_utils(gcd_env);
endclass: gcd_env

function void gcd_env::build_phase(uvm_phase phase);
    super.build_phase(phase);
    i_agt = gcd_agent::type_id::create("i_agt", this);
    o_agt = gcd_agent::type_id::create("o_agt", this);
    i_agt.is_active = UVM_ACTIVE;
    o_agt.is_active = UVM_PASSIVE;
    mdl = gcd_re_model::type_id::create("mdl", this);
    scb = gcd_scb::type_id::create("scb", this);
    agt_scb_fifo = new("agt_scb_fifo", this);
    agt_mdl_fifo = new("agt_mdl_fifo", this);
    mdl_scb_fifo = new("mdl_scb_fifo", this);
endfunction: build_phase

function void gcd_env::connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    i_agt.ap.connect(agt_mdl_fifo.analysis_export);//①
    mdl.port.connect(agt_mdl_fifo.blocking_get_export);//②
    mdl.ap.connect(mdl_scb_fifo.analysis_export);//③
    scb.mdl_port.connect(mdl_scb_fifo.blocking_get_export);//④
    o_agt.ap.connect(agt_scb_fifo.analysis_export);//⑤
    scb.mon_port.connect(agt_scb_fifo.blocking_get_export);//⑥
endfunction: connect_phase
`endif

连接关系:

  • i_agt所捕获到的事务数据发送到 agt_mdl_fifo,供后续模块使用
  • agt_mdl_fifo 中以阻塞方式获取输入事务数据
  • ③将模型模块生成的事务数据传递到 mdl_scb_fifo
  • ④从 mdl_scb_fifo 获取模型模块的输出事务数据,通常用于对比检查(验证设计行为是否符合预期)。
  • ⑤输出代理将其监控的事务数据发布到 agt_scb_fifo
  • ⑥从 agt_scb_fifo 获取输出代理的数据,以便与期望结果进行比较

下面是scoreboard,从两个fifo中拿数据对比

1
2
    uvm_blocking_get_port #(gcd_tr) mon_port;
    uvm_blocking_get_port #(gcd_tr) mdl_port;
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
`ifndef GCD_SCB__SV
`define GCD_SCB__SV

class gcd_scb extends uvm_scoreboard;
    uvm_blocking_get_port #(gcd_tr) mon_port;
    uvm_blocking_get_port #(gcd_tr) mdl_port;
    int check_num;
    `uvm_component_utils(gcd_scb)
    function new(string name = "gcd_scb", uvm_component parent = null);
        super.new(name, parent);
    endfunction: new
    
    extern virtual function void build_phase(uvm_phase phase);
    extern virtual task main_phase(uvm_phase phase);
    extern virtual function void check_phase(uvm_phase phase);
endclass: gcd_scb

function void gcd_scb::build_phase(uvm_phase phase);
    super.build_phase(phase);
    mon_port = new("mon_port", this);
    mdl_port = new("model_port", this);
    `uvm_info("GCD/SCB/BUILD", "The scoreboard has been built", UVM_LOW);
endfunction: build_phase

function void gcd_scb::check_phase(uvm_phase phase);
    super.check_phase(phase);
    `uvm_info("GCD/SCB/CEHCK", $psprintf("%0d item(s) have(has) been checked by scoreboard", check_num), UVM_LOW);
endfunction: check_phase
  
task gcd_scb::main_phase(uvm_phase phase);
    gcd_tr mdl_tr, mon_tr;
    bit result;
    check_num = 0;
    super.main_phase(phase);

    while(1)begin
        mon_port.get(mon_tr);
        mdl_port.get(mdl_tr);
        result = mon_tr.compare(mdl_tr);
        if(result) begin
            `uvm_info("GCD/SCB/MAIN", "Compare SUCCESSFUL", UVM_LOW);
        end else begin
            `uvm_error("GCD/SCB/MAIN", "Compare FATILED");
            $display("The model pkt is");
            mdl_tr.print();
            $display("The mon pkt is");
            mon_tr.print();
        end
        check_num++;
    end
endtask: main_phase
`endif

下面是refrence_model,有两个fifo接口

1
2
    uvm_blocking_get_port #(gcd_tr) port;//从agt_mdl_fifo获取输入的数
    uvm_analysis_port #(gcd_tr) ap;//往mdl_scb_fifo写golden值
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
69
70
71
72
73
`ifndef GCD_RE_MODEL__SV
`define GCD_RE_MODEL__SV

class gcd_re_model extends uvm_component;
    int re_num;
    uvm_blocking_get_port #(gcd_tr) port;
    uvm_analysis_port #(gcd_tr) ap;
    
    function new(string name = "gcd_re_model", uvm_component parent = null);
        super.new(name, parent);
    endfunction: new

    extern function void build_phase(uvm_phase phase);
    extern virtual task main_phase(uvm_phase phase);
    extern function void check_phase(uvm_phase phase);
    extern virtual task gcd_re_proc(gcd_tr tr);
    `uvm_component_utils(gcd_re_model)
endclass: gcd_re_model

function void gcd_re_model::build_phase(uvm_phase phase);
    super.build_phase(phase);
    port = new("port", this);
    ap = new("ap", this);
    `uvm_info("GCD/RE_MODEL/BUILD", "The reference model has been built", UVM_LOW);
endfunction: build_phase

function void gcd_re_model::check_phase(uvm_phase phase);
    super.check_phase(phase);
    `uvm_info("GCD/RE_MODEL/CEHCK", $psprintf("%0d item(s) have(has) been processed by the re-model", re_num), UVM_LOW);
endfunction: check_phase

task gcd_re_model::main_phase(uvm_phase phase);
    gcd_tr tr;
    super.main_phase(phase);
    re_num = 0;
    while(1) begin
        port.get(tr);
        `uvm_info("GCD/RE_MODEL/MAIN", "get one tr", UVM_LOW);
        gcd_re_proc(tr);
        re_num++;
    end
endtask: main_phase

task gcd_re_model::gcd_re_proc(gcd_tr tr);
    gcd_tr after_tr;
    bit [31:0]  devidend;
    bit [31:0]  divisor;
    bit [31:0]  remainder;
    after_tr = new("after_tr");
    after_tr.copy(tr);
    after_tr.result = 0;

    if(tr.opa > tr.opb) begin
        devidend = tr.opa;
        divisor = tr.opb;
    end else begin
        devidend = tr.opb;
        divisor = tr.opa;
    end

    while(1) begin
        remainder = devidend % divisor;
        if(remainder == 0)
            break;
        devidend = divisor;
        divisor = remainder;
    end

    after_tr.result = divisor;
    ap.write(after_tr);
    `uvm_info("GCD/RE_MODEL/PROC", "processed one tr and sent", UVM_LOW);
endtask: gcd_re_proc
`endif

port.get(tr):port的值赋给tr


env搭建完了,接下来是testcase

testcase

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
`ifndef BASE_TEST__SV
`define BASE_TEST__SV

class base_test extends uvm_test;
    gcd_env     env;
    function new(string name = "base_test", uvm_component parent = null);
        super.new(name, parent);
    endfunction

    extern virtual function void build_phase(uvm_phase phase);
    extern virtual function void report_phase(uvm_phase phase);
    `uvm_component_utils(base_test);
endclass: base_test

function void base_test::build_phase(uvm_phase phase);
    super.build_phase(phase);
    env = gcd_env::type_id::create("env", this);
endfunction: build_phase
  
function void base_test::report_phase(uvm_phase phase);
    uvm_report_server server;
    int err_num;
    super.report_phase(phase);
    
    server = get_report_server();
    err_num = server.get_severity_count(UVM_ERROR);

    if(err_num != 0) begin
        $display("TEST CASE FAILED");
    end else begin
        $display("TEST CASE PASSED");
    end
endfunction: report_phase
`endif

没什么好说的,套模板。需要在上述基础上构建自己项目的testcase


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
`ifndef GCD_RANDOM_TC__SV
`define GCD_RANDOM_TC__SV

class gcd_random_seq extends uvm_sequence #(gcd_tr);
    gcd_tr tr;
    function new(string name = "gcd_random_seq");
        super.new(name);
    endfunction: new

    virtual task body();
        if(starting_phase != null)
            starting_phase.raise_objection(this);
        repeat(100000) begin
            `uvm_do(tr);
        end
        #100
        if(starting_phase != null)
            starting_phase.drop_objection(this);
    endtask: body
    `uvm_object_utils(gcd_random_seq)
endclass: gcd_random_seq

class gcd_random_tc extends base_test;
    function new(string name = "gcd_random_tc", uvm_component parent = null);
        super.new(name, parent);
    endfunction: new

    extern virtual function void build_phase(uvm_phase phase);
    `uvm_component_utils(gcd_random_tc)
endclass: gcd_random_tc

function void gcd_random_tc::build_phase(uvm_phase phase);
    super.build_phase(phase);
    uvm_config_db#(uvm_object_wrapper)::set(this,
                                            "env.i_agt.sqr.main_phase",
                                            "default_sequence",
                                            gcd_random_seq::type_id::get());
endfunction: build_phase
`endif

uvm_sequencegcd_random_seq在body中用于产生随机激励。它包含激励生成并执行激励。

raise_objection()drop_objection() 用于控制仿真过程,表示仿真在运行这个序列时需要延长时间。raise_objection() 通常在开始激励时调用,drop_objection() 在激励完成后调用,以表示当前的激励完成。

uvm_do(tr) 宏用于生成一个transaction,并将其发送到与序列器相连的 driver 中

testbench

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
`include "uvm_macros.svh"
import uvm_pkg::*;

`include "gcd_if.sv"
`include "gcd_tr.sv"

module top_tb;
reg clk;
wire        reset;
wire [31:0] opa;
wire [31:0] opb;
wire        start;
wire        done;
wire [31:0] result;

gcd_if input_if(clk);
gcd_if output_if(clk);

GCD dut(
    .CLK(clk),
    .RESET(reset),
    .OPA(opa),
    .OPB(opb),
    .START(start),
    .DONE(done),
    .RESULT(result)
);

assign reset = input_if.reset;
assign opa = input_if.opa;
assign opb = input_if.opb;
assign start = input_if.start;
assign input_if.done = done;
assign input_if.result = 0;
assign output_if.opa = opa;
assign output_if.opb = opb;
assign output_if.start = start;
assign output_if.result = result;
assign output_if.done = done;

initial begin
    clk = 0;
    forever begin
        #10 clk = ~clk;
    end
end

initial begin
    run_test();
    #1000000
    $display("TIMEOUT");
    $finish();
end

initial begin
    uvm_config_db#(virtual gcd_if)::set(null, "uvm_test_top.env.i_agt.drv", "drv_if", input_if);
    uvm_config_db#(virtual gcd_if)::set(null, "uvm_test_top.env.i_agt.mon", "mon_if", input_if);
    uvm_config_db#(virtual gcd_if)::set(null, "uvm_test_top.env.o_agt.mon", "mon_if", output_if);

end

initial begin
    $fsdbDumpfile("gcd.fsdb");
    $fsdbDumpvars;
end
endmodule
This post is licensed under CC BY 4.0 by the author.