本文主要是介绍NVDLA uvm验证环境深度解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1.nvdla hw master github
- https://github.com/nvdla/hw/tree/master
- NVDLA Environment Setup Guide
- 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 SSSSSor*******************************
** 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:
| 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_ENABLEuvm_analysis_port#(memory_model_command) secondary_memory_model_command_port;
`endif变量声明部分代码
end_of_elaboration_phase中调用parse_trace函数,生成四种cmd file生成,然后分别读取其中内容,因为四种cmds每个字段的含义有些不同,请参考下图:
每一个cmds文件的处理大同小异,下面以sequence ctrl的处理为例:
//---------- sequence controllerbeginstring 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) );endwhile (!$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 nameseq_cmd.field_name = field_name;seq_cmd.data = data;seq_cmd.sync_id = sync_id;if ("NOTIFY" == kind_name) beginglobal_event_pool.get(sync_id);endsend_command_to_sequence(seq_cmd);endend$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 levelparameter 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:128parameter CACC_PW = (32*`NVDLA_SDP_MAX_THROUGHPUT+2); // small:34, large:514endpackage: 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))) beginwhile(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)endend
endtask : main_phasetask nvdla_tb_intr_handler::wait_intr(bit is_rm);if(is_rm) beginrm_intr_evt.wait_on();endelse begindut_intr_evt.wait_on();end`uvm_info(tID, $sformatf("intr_evt is on"), UVM_MEDIUM)
endtask : wait_intrtask 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_phasetask nvdla_tb_result_checker::warden_process();uint32_t cmd_idx;for (cmd_idx = 0; cmd_idx < command_number; cmd_idx ++) beginautomatic uint32_t var_i = cmd_idx;forkbeginuvm_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))endevt = global_event_pool.get(cmd_item.sync_id);if (evt.is_off()) beginevt.wait_on();endif (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) beginprimary_memory_check_command_port.write(cmd_item);end else beginsecondary_memory_check_command_port.write(cmd_item);endendendjoin_noneendwait fork;// Simulation complete notificationsim_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) beginevt = new(item.sync_id);glb_evts.add(item.sync_id, evt);endblk = item.block_name;blk_cmd[blk].push_back(item);end
endfunction : cmd_distributetask 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]) beginautomatic string j = i;forkbeginsequence_command cmd;while(blk_cmd[j].size() != 0) begincmd = 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)endjoin_noneendwait fork;`uvm_info(tID, $sformatf("main_phase complete ..."), UVM_MEDIUM)phase.drop_objection(this);
endtask : main_phasetask 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_issuetask 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))endregs = blks.get_reg_by_name(reg_name.toupper);if(regs == null) begin`uvm_fatal(tID, $sformatf("No exists uvm_reg: %s", reg_name))endregs.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 functionsextern function bit [1023:0] read(addr_t addr, bit [10:0] size_in_bits);// Write functionsextern function void write(addr_t addr, bit [1023:0] data, bit [10:0] size_in_bits, bit [127:0] wstrb);// Surface Load, Dump & Release functionsextern 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 APIsextern 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 functionsextern 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 fifouvm_tlm_analysis_fifo#(result_checker_command) rcc_fifo;// Memory read/write socketuvm_tlm_b_target_socket#(mem_wrap, gp_t) skt;// Store requested memory regionsmem_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);
endclasstask mem_wrap::run_phase(uvm_phase phase);super.run_phase(phase);`uvm_info(tID, $sformatf("run_phase begin ..."), UVM_HIGH)forkbeginmemory_model_command tr;forever beginmmc_fifo.get(tr);m_process_memory_model_command(tr);endendbeginresult_checker_command tr;forever beginrcc_fifo.get(tr);m_process_result_checker_command(tr);endendjoin`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有空再更新吧。
这篇关于NVDLA uvm验证环境深度解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!