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。參考如下:

Name Description Syntax Use case
reg_write Write data to specific DUT register reg_write(reg_n ame, reg_value); Fundamental operation for register configuration
reg_read_expect ed Read data from specific DUT register, compare with expected value reg_read_expect ed(addr, expected_data); For some special cases like register accessing tests
reg_read Read data from specific DUT register reg_read(reg_na me, return_value); For specific cases which may need to do post-processing on read return value.
sync_notify Specified player sequencer will send out synchronization event sync_notify(tar get_resource, sync_id); CC pipeline, OP_EN configuration order, CACC->CMAC->CSC .
sync_wait Specified player sequencer will wait on synchronization event sync_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有空再更新吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章