NVDLA uvm验证环境深度解析

1.nvdla hw master github

  1. https://github.com/nvdla/hw/tree/master
  2. NVDLA Environment Setup Guide
  3. NVDLA Verification Suite User Guide

首先checkout nvdla hw master breach,然后按照第2,3步,完成build和health check,当nvdla tree build完之后,通过run testcase来check环境是否完善,build是否成功。

仿真结束后,在log的结尾会打印如下pass fail 信息:

*******************************
**        TEST PASS          **
*******************************


PPPP     A     SSSSS  SSSSS
P   P   A A    S      S
PPPP   AAAAA   SSSSS  SSSSS
P     A     A      S      S
P     A     A  SSSSS  SSSSS

or

*******************************
**        TEST FAILED        **
*******************************


FFFFF    A     IIIII  L
F       A A      I    L
FFFF   AAAAA     I    L
F     A     A    I    L
F     A     A  IIIII  LLLLL

2. 测试套件

主要有两类tests,一类是直接测试,一类是uvm的受约束的随机测试(constrained-random tests)。

 NV_SMALL project的直接测试路径为:TOT/verif/tests/trace_tests/nv_small

受约束的随机测试的路径为:TOT/verif/tests/trace_tests/uvm_tests

*目前为止,只有直接测试是官方ready的

3. 直接测试举例

以TOT/verif/tests/trace_tests/nv_small/cdp_1x1x1_lrn3_int8_0/为例,对直接测试进行简单讲解。

直接测试是以一种自定义的trace format的形式编写的,nvdla 定义了9种configuration commands。参考如下:

NameDescriptionSyntaxUse case
reg_writeWrite data to specific DUT registerreg_write(reg_n ame, reg_value);Fundamental operation for register configuration
reg_read_expect edRead data from specific DUT register, compare with expected valuereg_read_expect ed(addr, expected_data);For some special cases like register accessing tests
reg_readRead data from specific DUT registerreg_read(reg_na me, return_value);For specific cases which may need to do post-processing on read return value.
sync_notifySpecified player sequencer will send out synchronization eventsync_notify(tar get_resource, sync_id);CC pipeline, OP_EN configuration order, CACC->CMAC->CSC .
sync_waitSpecified player sequencer will wait on synchronization eventsync_wait(targe t_resource, sync_id);CC pipeline, OP_EN configuration order, CACC->CMAC->CSC .
intr_notify

Monitor DUT interrupt, catch and clear interrupt and send synchronization event.

There could be multiple intr_notify, all those intr_notify are processed sequentially. The processing order is the same as commands’ line order in configuration file.

intr_notify(int r_id, sync_id); // notify when specific interrupt fired

Hardware layer complete notification, informing test bench that test is ended.

Multi-layer test which is presumed containing layer 0 ~ N, for n >1 layers, they shall wait for interrupts.

poll

Continues poll register/field value from DUT, until one of the following conditions are met:

  1. Equal, polled value is equal to expected value
  2. Greater, polled value is greater than expected value
  3. Less, polled value is less than expected value
  4. Not equal, polled value is not equal to expected value
  5. Not greater, polled value is not greater than expected value
  6. Not less, polled value is not less than expected value

poll_field_equa l(target_resour ce, register_name, field_name, expected_value) ;

poll_reg_equal( target_resource , register_name, expected_value) ;

poll_field_grea ter(target_reso urce, register_name, field_name, expected_value) ;

poll_reg_less(t arget_resource, register_name, expected_value) ;

poll_field_nt_ greater(taget_ resource, register_name, field_name, expected_value) ;

poll_reg_not_le ss(target_resou rce, register_name, expected_value) ;

Convolution case, wait until CBUF flush has done
check

Invoke player result checking method.

When test bench works in RTL/CMOD cross checking mode, neither golden CRC nor golden files are necessary in this case. Method check_nothing() shall be added to trace file to indicated test end event.

check_crc(syn_ id, memory_type, base_address, size, golden_crc_valu e);

check_file(sync _id, memory_type, base_address, size, “golden_file_na me”);

check_nothing(s ync_id);

CRC check for no CMOD simulation (usually generated by arch/inherit from previous project/eyeball gilded)

Golden memory result check for no CMOD simulation (usually generated by arch/inherit from previous project/eyeball gilded)

mem

Load memory from file.

Initialize memory by pattern.

mem_load(ram_ty pe, base_addr, file_path); // file_path shall be enclosed by “”

mem_init(ram_ty pe, base_addr, size, pattern);

 

*Some functions are not supported yet.

直接测试trace format如下:

mem_init(pri_mem, 0x80000c00, 0x800, ALL_ZERO);
mem_load(pri_mem, 0x80000c00, "cdp_1x1x1_lrn3_int8_0_in.dat");
mem_init(pri_mem, 0x80000020, 0x800, ALL_ZERO);
reg_write(NVDLA_CDP.S_POINTER_0, 0x1);
reg_read_check(NVDLA_CDP.S_POINTER_0, 0x1);
/*
some reg_write cfg cmd for dla reg config
/*
intr_notify(CDP_0, sync_id_0);
check_crc(sync_id_0, 1, 0x80000020, 0x800, 0xf1e8ba9e);

add a new cfg command:
mem_reserve(pri_mem, 0x80000020, 0x800);
poll_reg_equal(NVDLA_GLB.S_INTR_STATUS_0, 0x40);

trace format 文件,在trace_player tb中经过nvdla_trace_parser.py脚本处理,会生成四个文件,四个文件分别对应四类不同的cfg cmds。

文件内容分别如下:

4. trace_player tb 关键组件解释

4.1 tb 验证组件一览:

.
├── Makefile                   # 编译makefile
├── nvdla_tb_base_test.sv      # base test, 例化 env  trace_parser seq result_ck
|                              # 并完成tlm port连接
├── nvdla_tb_common.svh        # 宏定义,以及用package封装tb中使用的参数
├── nvdla_tb_connect.sv        # tb用interface和dut各个模块的连接
├── nvdla_tb_env.sv            # 
├── nvdla_tb_intr_handler.sv   # 中断处理类
├── nvdla_tb_override.sv       # tlm generic payload 重载类
├── nvdla_tb_result_checker.sv # result check类,实现并行check,以及于其他cmds的同步
├── nvdla_tb_scoreboard.sv     # 很明显了,计分板
├── nvdla_tb_sequence.sv       # 寄存器读写以及check
├── nvdla_tb_top.sv            # tb top
├── nvdla_tb_trace_parser.sv   # trace format文件的parser类
└── nvdla_tb_txn.sv            # 定义tb中用到的对应四种cmds的四种sequence,
                               # 以及define trace parser instruction command enum type

4.2 nvdla_tb_trace_parser

这一切都是在nvdla_tb_trace_parser.sv类中完成的,

class nvdla_tb_trace_parser extends uvm_component;

    string      tID = "nvdla_tb_trace_parser";
    string      trace_file_path;
    string      parser_core_path  = "nvdla_trace_parser.py";
    string      seq_cmd_file_path = "./trace_parser_cmd_sequence_command.log";
    string      ic_cmd_file_path  = "./trace_parser_cmd_interrupt_controller_command.log";
    string      rc_cmd_file_path  = "./trace_parser_cmd_result_checker_command.log";
    string      mm_cmd_file_path  = "./trace_parser_cmd_memory_model_command.log";
    uint32_t    echo_line_content =1;
    uvm_event_pool                              global_event_pool;
    uvm_analysis_port#(sequence_command)        sequence_command_port; 
    uvm_analysis_port#(result_checker_command)  result_checker_command_port; 
    uvm_analysis_port#(interrupt_command)       interrupt_handler_command_port; 
    uvm_analysis_port#(memory_model_command)    primary_memory_model_command_port; 
`ifdef NVDLA_SECONDARY_MEMIF_ENABLE
    uvm_analysis_port#(memory_model_command)    secondary_memory_model_command_port; 
`endif

变量声明部分代码

end_of_elaboration_phase中调用parse_trace函数,生成四种cmd file生成,然后分别读取其中内容,因为四种cmds每个字段的含义有些不同,请参考下图:

每一个cmds文件的处理大同小异,下面以sequence ctrl的处理为例:

    //---------- sequence controller
    begin
        string      block_name;
        string      reg_name;
        string      field_name;
        uint32_t    data;
        string      sync_id;
        fh = $fopen(seq_cmd_file_path,"r");
        if(!fh) begin
            `uvm_fatal(tID, $sformatf("Cannot find sequence command file %s\n", seq_cmd_file_path) );
        end
        while (!$feof(fh)) begin
            // string kind_name,block_name,reg_name,field_name,data,sync_id;
            code = $fscanf(fh,"%s %s %s %s %h %s\n",kind_name,block_name,reg_name,field_name,data,sync_id);
            if (6 == code) begin
                `uvm_info(tID,$sformatf("kind_name=%s, block_name=%s, reg_name=%s, field_name=%s, data=%h, sync_id=%s",kind_name,block_name,reg_name,field_name,data,sync_id), UVM_HIGH);
                seq_cmd = new("seq_cmd");
                kind_wrapper::from_name(kind_name, seq_cmd.kind);
                seq_cmd.block_name  = block_name;
                seq_cmd.reg_name    = reg_name.substr(0, reg_name.len()-3); //FIXME: no "_0" suffix in ral register name
                seq_cmd.field_name  = field_name;
                seq_cmd.data        = data;
                seq_cmd.sync_id     = sync_id;
                if ("NOTIFY" == kind_name) begin
                    global_event_pool.get(sync_id);
                end
                send_command_to_sequence(seq_cmd);
            end
        end
        $fclose(fh);
    end

主要调用verilog system api $fopen,  $fscanf, $sscanf, $feof, $fclose, 完成文件打开关闭,每行按格式读取和检查文件结尾标志。

文件中的一行,就是一个完整的sequence cmd,对应到tb中,sequence_command类中有对应每一行每个字段的变量,

这个kind_wrapper有点意思。

typedef enum {   WRITE
               , NOTIFY
               , WAIT
               , READ
               , READ_CHECK
               , POLL_REG_EQUAL
               , POLL_FIELD
               , SINGLE_SHOT
               , MULTI_SHOT
               , CHECK_CRC
               , CHECK_FILE
               , CHECK_NOTHING
               , MEM_RESERVE
               , MEM_LOAD
               , MEM_INIT_PATTERN
               , MEM_INIT_FILE
               , MEM_RELEASE
             } kind_e;

typedef enum { PRI_MEM    = 0,
               SEC_MEM    = 1
             } memory_type_e;

typedef uvm_enum_wrapper#(kind_e)           kind_wrapper;
typedef uvm_enum_wrapper#(memory_type_e)    memory_type_wrapper;

// Class: uvm_enum_wrapper#(T)
//
// The ~uvm_enum_wrapper#(T)~ class is a utility mechanism provided
// as a convenience to the end user.  It provides a <from_name>
// method which is the logical inverse of the System Verilog ~name~ 
// method which is built into all enumerations.
// Function: from_name
// Attempts to convert a string ~name~ to an enumerated value.
//
// If the conversion is successful, the method will return
// 1, otherwise 0.
//
// Note that the ~name~ passed in to the method must exactly
// match the value which would be produced by ~enum::name~, and
// is case sensitive.

kind_wrapper::from_name(kind_name, seq_cmd.kind);

意思将字符串转换为自定义的枚举类型变量。上图中kind_name是字符串,seq_cmd.kind是枚举类型变量。

然后调用send_command_to_sequence函数,通过uvm_analysis_port#(sequence_command)  sequence_command_port建cmd sequence发送到nvdla_tb_sequence类中取处理。

function void nvdla_tb_trace_parser::send_command_to_sequence(sequence_command cmd);
    `uvm_info(tID, $sformatf("seq cmd:%0s", cmd.sprint()), UVM_MEDIUM)
    sequence_command_port.write(cmd);
endfunction : send_command_to_sequence

4.3 nvdla_tb_common

package nvdla_tb_common_pkg;

    //Define global message print verbosity macros
    //Compatiable with UVM_VERBOSITY and extends to add two more print level
    parameter  NVDLA_NONE   =  0;
    parameter  NVDLA_LOW    =  100;
    parameter  NVDLA_MEDIUM =  200;
    parameter  NVDLA_TRACE  =  250;
    parameter  NVDLA_VERIF  =  280;
    parameter  NVDLA_HIGH   =  300;
    parameter  NVDLA_FULL   =  400;
    parameter  NVDLA_DEBUG  =  500;

    // TODO: Remove hard-coded magic numbers and use macros from project.vh once VMOD add them.


    parameter SDP_DW = `NVDLA_BPE;
    parameter SDP_DS = `NVDLA_SDP_MAX_THROUGHPUT;
    parameter SDP_PW    = (`NVDLA_BPE*`NVDLA_SDP_MAX_THROUGHPUT);  // small:8, large:128
    parameter CACC_PW   = (32*`NVDLA_SDP_MAX_THROUGHPUT+2);   // small:34, large:514

endpackage: nvdla_tb_common_pkg

用包封装tb中使用的参数,符合uvm的验证哲学,使用的时候,只需 import nvdla_tb_common_pkg::*;即可。

4.4 nvdla_tb_intr_handler

// TASK: main_phase
// Used to execure mainly run-time tasks of simulation
task nvdla_tb_intr_handler::main_phase(uvm_phase phase);
    super.main_phase(phase);
    `uvm_info(tID, $sformatf("main_phase begin ..."), UVM_HIGH)

    // `uvm_info(tID, $sformatf("raise objection"), UVM_MEDIUM)
    if((is_rm && ("RTL_ONLY" != work_mode)) || (!is_rm && ("CMOD_ONLY" != work_mode))) begin
        while(cmd_queue_size()>0) begin
            `uvm_info(tID, $sformatf("raise objection"), UVM_MEDIUM)
            phase.raise_objection(this);
            intr_process();
            phase.drop_objection(this);
            `uvm_info(tID, $sformatf("drop objection"), UVM_MEDIUM)
        end
    end
endtask : main_phase


task nvdla_tb_intr_handler::wait_intr(bit is_rm);
    if(is_rm) begin
        rm_intr_evt.wait_on();
    end
    else begin
        dut_intr_evt.wait_on();
    end
    `uvm_info(tID, $sformatf("intr_evt is on"), UVM_MEDIUM)
endtask : wait_intr

task nvdla_tb_intr_handler::intr_process();
    bit [`CSB_DATA_WIDTH-1:0]    intr_val;
    bit [`CSB_DATA_WIDTH-1:0]    mask_val;
    uvm_reg_field     flds[$];
    interrupt_command item;
    uvm_tlm_gp        gp;
    int               lsb;

    wait_intr(is_rm);
    get_intr_val(is_rm, intr_val, mask_val);

    flds.delete();
    ral.nvdla.NVDLA_GLB.S_INTR_STATUS.get_fields(flds);
    foreach(flds[i]) begin

    /*省略部分代码*/
endtask

首先在nvdla_tb_top.sv中有如下代码:

dla_intr即为dla中断输出信号,每当中断assert,需要将global event pool中的dut_intr_evt trigger,并且等待uvm_event的wait_off方法。 

其次在nvdla_tb_intr_handler的intr_process方法中,调用wait_intr等待dut_intr_evt trigger,说明此时有中断产生, 调用get_intr_val拿到S_INTR_STATUS和S_INTR_MASK寄存器的值,由于有两个reg group,需要判断是哪个类型的两个中断中的哪个。例如:

intr_notify(CDP_0, sync_id_0); intr_notify(CDP_1, sync_id_0); 遍历S_INTR_STATUS寄存器每个bit的值,得到reg model中每个reg field的名字,并且操作字符串得到act_id或者blk_name,与CDP_0 CDP_1中的CDP模块名比较,还有0或者1进行比较。

最后调用evt_trigger触发同步事件,并且调用intr_clear 清除中断标志位,调用evt_reset复位dut_intr_evt 事件,使上图中的evt.wait_off生效。

4.5 nvdla_tb_result_checker

task nvdla_tb_result_checker::main_phase(uvm_phase phase);
    super.main_phase(phase);
    `uvm_info(tID, $sformatf("main_phase begin ..."), UVM_MEDIUM)
    phase.raise_objection(this);
    warden_process();
    phase.drop_objection(this);
    `uvm_info(tID, $sformatf("main_phase end ..."), UVM_MEDIUM)

endtask : main_phase

task nvdla_tb_result_checker::warden_process();
    uint32_t cmd_idx;
    for (cmd_idx = 0; cmd_idx < command_number; cmd_idx ++) begin
        automatic uint32_t var_i = cmd_idx;
        fork
            begin
                uvm_event evt;
                result_checker_command cmd_item;
                command_fifo.get(cmd_item);
                if(!global_event_pool.exists(cmd_item.sync_id)) begin
                    `uvm_fatal(tID, $sformatf("sync_id %0s doesn't exist", cmd_item.sync_id))
                end
                evt = global_event_pool.get(cmd_item.sync_id);
                if (evt.is_off()) begin
                    evt.wait_on();
                end
                if (CHECK_NOTHING == cmd_item.kind) begin
                    `uvm_info(tID, $sformatf("No need to check on sync_id:%s.", cmd_item.sync_id), UVM_MEDIUM)
                end else begin
                    `uvm_info(tID, $sformatf("Ready to send check command to memory model ..."), UVM_MEDIUM)
                    if (PRI_MEM == cmd_item.memory_type) begin
                        primary_memory_check_command_port.write(cmd_item);
                    end else begin
                        secondary_memory_check_command_port.write(cmd_item);
                    end
                end
            end
        join_none
    end
    wait fork;
    
    // Simulation complete notification
    sim_done_evt.trigger();

endtask : warden_process

nvdla_tb_intr_handler触发了同步事件sync_id_0, check_crc(sync_id_0, 1, 0x80000020, 0x800, 0xf1e8ba9e) 中第一个字段,就是需要同步等待的事件,说明模块done中断来了,可以进行结果比较了。再根据mem type是primary mem还是secondary mem将cmd item写到不同的组件进行处理(其实是一个组件例化两份)。再比较的进程都结束后,触发sim_done_evt,在nvdla_tb_top中有对sim_done_evt的应用。

注意:warden_process方法中用的fork join_none wait fork的语法

4.6 nvdla_tb_sequence

function void nvdla_tb_sequence::cmd_distribute();
    sequence_command  item_t;    
    sequence_command  item;    
    string            blk;
    uvm_event         evt;

    while(cmd_fifo.try_get(item_t))begin
        $cast(item, item_t.clone());
        `uvm_info(tID, $sformatf("cmd_distribute:\n%0s", item.sprint()), UVM_HIGH)
        if(item.kind == NOTIFY || item.kind == WAIT) begin
            evt = new(item.sync_id);
            glb_evts.add(item.sync_id, evt);
        end
        blk = item.block_name;
        blk_cmd[blk].push_back(item);
    end
endfunction : cmd_distribute

task nvdla_tb_sequence::main_phase(uvm_phase phase);
    super.main_phase(phase);
    `uvm_info(tID, $sformatf("main_phase begin ..."), UVM_MEDIUM)
    phase.raise_objection(this);
    foreach(blk_cmd[i]) begin
        automatic string j = i;
        fork
            begin
                sequence_command cmd;
                while(blk_cmd[j].size() != 0) begin
                    cmd = blk_cmd[j].pop_front();
                    cmd_issue(cmd);
                end
                `uvm_info(tID, $sformatf("main_phase::sequence %s, all commands have been procesdded.", j), UVM_MEDIUM)
            end
        join_none
    end
    wait fork;
    `uvm_info(tID, $sformatf("main_phase complete ..."), UVM_MEDIUM)
    phase.drop_objection(this);
endtask : main_phase

task nvdla_tb_sequence::cmd_issue(sequence_command cmd);
    `uvm_info(tID, $sformatf("cmd_issue start:%0s", cmd.sprint()), UVM_MEDIUM)
    case(cmd.kind) 
        WRITE:      write_i(cmd);
        NOTIFY:     notify_i(cmd);
        WAIT:       wait_i(cmd);
        READ:       read_i(cmd);
        READ_CHECK: read_check_i(cmd);
        POLL_REG_EQUAL:  poll_reg_equal_i(cmd);
        POLL_FIELD: poll_field_i(cmd);
    endcase
    `uvm_info(tID, $sformatf("cmd_issue done."), UVM_MEDIUM)
endtask : cmd_issue


task nvdla_tb_sequence::write_i(sequence_command cmd);
    string          blk_name;
    string          reg_name;
    uvm_reg         regs;
    uvm_reg_block   blks;
    uvm_status_e    status;
    
    blk_name = cmd.block_name;
    reg_name = cmd.reg_name;
    blks = ral.nvdla.get_block_by_name(blk_name.toupper);
    if(blks == null) begin
        `uvm_fatal(tID, $sformatf("No exists uvm_reg_block: %s", blk_name))
    end
    regs = blks.get_reg_by_name(reg_name.toupper);
    if(regs == null) begin
        `uvm_fatal(tID, $sformatf("No exists uvm_reg: %s", reg_name))
    end
    regs.write(status, cmd.data);

endtask : write_i

在start_of_simulation_phase中调用cmd_distribute函数,将cmd_fifo中的cmd_item按照block_name为索引,扔进blk_cmd,一个队列类型的联合数组里。在main_phase()中,遍历blk_cmd,pop出cmd_item,然后调用cmd_issue,执行cmd。注意,使用fork join_none wait_fork。这样使得不同blk的寄存器同时进行配置,但是一个blk内的若干寄存器是按顺序进行配置的。

cmd_issue方法中有几种不同了类型的cmd的处理,我们以write_i为例进行说明。

根据cmd_item中的block_name,reg_name,分别调用get_block_by_name,get_reg_by_name拿到reg_block再拿到regs,最后调用 regs.write实现,寄存器的前门写。

4.7 与result check相关的mem_model

mem_model包主要有mem_core和mem_wrap两个类组成。

class mem_core extends uvm_component;
    typedef enum {RANDOM, ZEROS, ONES, X, AFIVE, FIVEA, ADDR} init_option_enum;

    string tID;

    rand init_option_enum init_option = RANDOM;
    rand bit dont_store_uninitialized_vals = 0;
    addr_t      base;
    addr_t      limit;

    protected bit [7:0] m_mem[addr_t];

    // Read functions
    extern function bit [1023:0] read(addr_t addr, bit [10:0] size_in_bits);

    // Write functions
    extern function void write(addr_t addr, bit [1023:0] data, bit [10:0] size_in_bits, bit [127:0] wstrb);

    // Surface Load, Dump & Release functions
    extern function void load_surface(string filename, addr_t base);
    extern function void dump_surface(string filename, addr_t base, int unsigned len);
    extern function void init_surface_with_pattern(addr_t base, int unsigned len, string pattern);
    extern function void init_surface_with_pattern_and_file(addr_t base, string pattern, string filename);

    // Other APIs
    extern function bit mem_exists(addr_t addr);
    extern function bit has_addr(addr_t addr);
    extern function int unsigned calc_surface_crc(addr_t base, int len);

    // Private utility functions
    extern protected function string m_trimed_string(string str);
    extern protected function int m_atov(string str);
    extern protected function void m_parse_surface_file(string filename, output surface_file_content content);

endclass

mem_core中声明一个byte类型的联合数组,作为m_mem使用,用以存储数据。

init_option是为了读不存在的地址时,返回什么值的问题,同时由dont_store_uninitialized_vals变量决定将这个返回值,写到m_mem中对应地址。

base limit用来决定当前mem的可用基地址和大小

load_surface函数调用m_parse_surface_file函数,将mem surface格式的文件处理读取出有效的信息,例如offset地址,data信息。也就是往哪些地址写哪些数据,最后调用write8函数,将数据写到m_mem联合数组中。

dump_surface(string filename, addr_t base, int unsigned len)函数将base地址开始len个byte的数据,按照一定格式写到filename中。

init_surface_with_pattern(addr_t base, int unsigned len, string pattern)函数初始化base地址开始len个byte的m_mem数据,以pattern指定的格式(RANDOM, ALL_ZERO等)。

init_surface_with_pattern_and_file函数不给定len,有parse的file中信息,找到end_offset,然后调用init_surface_with_pattern函数完成初始化。

calc_surface_crc(addr_t base, int len)从base地址开始,读取len个byte的数据,计算crc值。

class mem_wrap extends uvm_component;

    typedef uvm_tlm_generic_payload gp_t;

    uvm_tlm_analysis_fifo#(memory_model_command) mmc_fifo;

    // result checker command fifo
    uvm_tlm_analysis_fifo#(result_checker_command) rcc_fifo;

    // Memory read/write socket
    uvm_tlm_b_target_socket#(mem_wrap, gp_t) skt;

    // Store requested memory regions
    mem_core mem_list[$];

    uvm_event_pool  global_event_pool;

    bit auto_dump_surface = 1;

    extern task b_transport(gp_t gp, uvm_tlm_time delay);

    extern protected task m_process_memory_model_command(memory_model_command tr);
    extern protected task m_process_result_checker_command(result_checker_command tr);

    extern local function void evaluate_memory_model_command(memory_model_command tr);
    extern local function string sprint_mem_list();
    extern local function mem_core locate_mem(addr_t addr);
endclass

task mem_wrap::run_phase(uvm_phase phase);
    super.run_phase(phase);
    `uvm_info(tID, $sformatf("run_phase begin ..."), UVM_HIGH)

    fork
        begin
            memory_model_command tr;
            forever begin
                mmc_fifo.get(tr);
                m_process_memory_model_command(tr);
            end
        end

        begin
            result_checker_command tr;
            forever begin
                rcc_fifo.get(tr);
                m_process_result_checker_command(tr);
            end
        end
    join

    `uvm_info(tID, $sformatf("run_phase complete ..."), UVM_HIGH)
endtask

skt是与dbb_slave_agent中driver中的mem_initiator连接,这样driver可以从mem_core读数据,然后发送给dut,或者从dut捕获数据,写到mem_core中的m_mem.

mem_list维护一个已经创建的mem_core的队列,以便重复访问时,可以直接调用。

在run_phase中并行循环调用m_process_memory_model_command和m_process_result_checker_command方法。

m_process_memory_model_command方法根据mem_cmd_item决定执行mem的哪种操作(MEM_RESERVE,MEM_LOAD,MEM_INIT_PATTERN,MEM_RELEASE),并调用mem_core中对应的函数完成m_mem的操作。

m_process_result_checker_command方法根据从result checker来的cmd_item的类型,来执行是CHECK_CRC还是CHECK_FILE操作。CHECK_CRC 调用mem_core的calc_surface_crc函数,计算得到的crc值和golden值进行比较,判断dla结果的正确与否。

总结:

本篇文章,主要还是讲解了trace_player tb的主要结构,并没有每个类每个函数都一一讲解,这有待大家主动学习,例如vip下的agent uvc都没有讲解,这块因为在dla集成的soc level的话,会有对应的vip来代替nvdla提供的uvc,所以并没有细看。至于trace_generator tb有空再更新吧。

UVM是一种面向对象的验证方法学,对于复杂的芯片验证任务,它具有高效可扩展的特点。搭建UVM验证环境可以有效提高芯片验证的效率和可靠性,下面就来介绍一下UVM验证环境搭建的实例。 1.准备工作 在搭建UVM验证环境前,需要准备好以下工具: • 模拟器:搭建UVM验证环境前需要先选定合适的模拟器。 • UVM库文件:从SystemVerilog UVM框架网站上下载UVM库文件,并将其添加到环境变量中。 • 仿真脚本:根据项目需求书写仿真脚本,通常使用Shell或Tcl脚本。 2.编写环境代码 根据项目需求,编写UVM验证环境代码,通常可以分为以下几个部分: • Testbench代码:包含了环境中的各种模块。 • Scoreboard代码:负责验证输入输出结果是否正确。 • Monitor代码:用于监测设计实例的波形输出。 • Agent代码:用于产生输入信号和接收输出信号。 3.配置和运行仿真 在编写完环境代码后,需要进行以下配置和运行仿真: • 设计时钟和重置时钟的设置,以确保仿真结果的正确性。 • 在仿真脚本中指定仿真模型和仿真测试程序。 • 设定仿真时间,确保仿真能够在能够完成所有的测试。 • 开始仿真并检查仿真结果。 总之,在搭建UVM验证环境时,需要准备好所需的工具,编写出符合项目需求的环境代码,配置和运行仿真。通过以上步骤,可以有效提高芯片验证的效率和可靠性。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值