目录
MCDF改造
APB接口
验证环境的改造
实战任务
apb_config
apb_master_agent
apb_master_driver
1.1实现apb_master_driver的drive_transfer()
apb_master_monitor
1.3编写apb_master_monitor中的collect_transfer()
apb_master_sequence_Lib
1.2编写apb_master_sequence中的apb_master_write_seq::body()任务
apb_master_sequencer
slave端
apb_slave_driver
1.4编写apb_slave_driver的drive_response()
apb_slave_seq_lib
为什么要让sequence控制driver?
这里涉及的是VIP的开发和MCDF用APB改造。
MCDF改造
所有的接口都发生了改变,对应在硬件中都做了修改。
APB接口
特点是一总(AHB或者AXI)多从,1总通过APB桥发放到后面的多从slave;2个clk完成1次W/R操作,用状态机表示为idle->setup->enable
验证环境的改造
由于接口几乎全部被更新,所以mcdf这一层的接口和tb的接口也需要做相应的更改。
这里似乎没有涉及到env的问题,等下看看具体代码。
实战任务
直接看到APB部分的文件夹,可以看到文件数量大大增加,采取了`include的方式将文件分开了。
apb_pkg只有简单的几行代码,信息都包含在别的文件中。
`ifndef APB_PKG_SV`define APB_PKG_SVpackage apb_pkg;import uvm_pkg::*;`include "uvm_macros.svh"`include "apb.svh"endpackage : apb_pkg`endif // `ifndef APB_PKG_SV
apb_pkg include了apb.svh,而后者include了主从的各个组件的sv和svh文件。
`ifndef APB_SVH`define APB_SVH`include "apb_transfer.sv"`include "apb_config.sv"`include "apb_master_driver.svh"`include "apb_master_monitor.svh"`include "apb_master_sequencer.svh"`include "apb_master_agent.svh"`include "apb_slave_driver.svh"`include "apb_slave_monitor.svh"`include "apb_slave_sequencer.svh"`include "apb_slave_agent.svh"`include "apb_master_driver.sv" `include "apb_master_monitor.sv"`include "apb_master_sequencer.sv"`include "apb_master_agent.sv"`include "apb_master_seq_lib.sv"`include "apb_slave_driver.sv" `include "apb_slave_monitor.sv"`include "apb_slave_sequencer.sv"`include "apb_slave_agent.sv"`include "apb_slave_seq_lib.sv"`endif // `ifndef APB_SVH
在apb_tb中,include了test.svh和if.sv,要跑起仿真只需要编译pkg和tb即可(其他文件都被include了,其实也会被编译到)。tb只做了接口的配置
`timescale 1ps/1psimport uvm_pkg::*;`include "uvm_macros.svh"`include "apb_tests.svh"`include "apb_if.sv"module apb_tb;bit clk, rstn;initial beginforkbegin forever #5ns clk = !clk;endbegin#100ns;rstn <= 1'b1;#100ns;rstn <= 1'b0;#100ns;rstn <= 1'b1;endjoin_noneendapb_if intf(clk, rstn);initial beginuvm_config_db#(virtual apb_if)::set(uvm_root::get(), "uvm_test_top.env.mst", "vif", intf);uvm_config_db#(virtual apb_if)::set(uvm_root::get(), "uvm_test_top.env.slv", "vif", intf);run_test("apb_single_transaction_test");endendmodule
在apb_if中,包括了接口+时钟块,覆盖组covergroup。接口信号按照时序图给出,时钟块分为master和slave,信号的方向互补,monitor的时钟块所有信号都是input。
这里又遇到覆盖率收集的问题,复习一下。定义覆盖组后再initial块中例化各个覆盖组。
/mp_blog/creation/editor/125731520
`ifndef APB_IF_SV`define APB_IF_SVinterface apb_if (input clk, input rstn);logic [31:0] paddr;logic pwrite;logic psel;logic penable;logic [31:0] pwdata;logic [31:0] prdata;// Control flagsbithas_checks = 1;bithas_coverage = 1;// Actual Signals // USER: Add interface signalsclocking cb_mst @(posedge clk);// USER: Add clocking block detaildefault input #1ps output #1ps;output paddr, pwrite, psel, penable, pwdata;input prdata;endclocking : cb_mstclocking cb_slv @(posedge clk);// USER: Add clocking block detaildefault input #1ps output #1ps;input paddr, pwrite, psel, penable, pwdata;output prdata;endclocking : cb_slvclocking cb_mon @(posedge clk);// USER: Add clocking block detaildefault input #1ps output #1ps;input paddr, pwrite, psel, penable, pwdata, prdata;endclocking : cb_mon// Coverage and assertions to be implemented here.// USER: Add assertions/coverage here// APB command covergroupcovergroup cg_apb_command @(posedge clk iff rstn);pwrite: coverpoint pwrite{type_option.weight = 0;bins write = {1};bins read = {0};}psel : coverpoint psel{type_option.weight = 0;bins sel = {1};bins unsel = {0};}cmd : cross pwrite, psel{bins cmd_write = binsof(psel.sel) && binsof(pwrite.write);bins cmd_read = binsof(psel.sel) && binsof(pwrite.read);bins cmd_idle = binsof(psel.unsel);}endgroup// APB transaction timing groupcovergroup cg_apb_trans_timing_group @(posedge clk iff rstn);psel: coverpoint psel{bins single = (0 => 1 => 1 => 0); bins burst_2 = (0 => 1 [*4] => 0); bins burst_4 = (0 => 1 [*8] => 0); bins burst_8 = (0 => 1 [*16] => 0); bins burst_16 = (0 => 1 [*32] => 0); bins burst_32 = (0 => 1 [*64] => 0); }penable: coverpoint penable {bins single = (0 => 1 => 0 [*2:10] => 1);bins burst = (0 => 1 => 0 => 1);}endgroup// APB write & read order groupcovergroup cg_apb_write_read_order_group @(posedge clk iff (rstn && penable));write_read_order: coverpoint pwrite{bins write_write = (1 => 1);bins write_read = (1 => 0);bins read_write = (0 => 1);bins read_read = (0 => 0);} endgroupinitial beginautomatic cg_apb_command cg0 = new();automatic cg_apb_trans_timing_group cg1 = new();automatic cg_apb_write_read_order_group cg2 = new();endendinterface : apb_if`endif // APB_IF_SV
关于test文件,画了个框图如下:
接下来再逐个看各个组件
apb_config
这是等下用来配置agent是active还是passive用的object
class apb_config extends uvm_object;`uvm_object_utils(apb_config)// USER to specify the config itemsuvm_active_passive_enum is_active = UVM_ACTIVE;function new (string name = "apb_config");super.new(name);endfunction : newendclass`endif // APB_CONFIG_SV
apb_master_agent
由于使用了头文件,这里的方法名都要加上apb_master_agent::
agent做了三件事:build()例化对象,connect()连接driver和sequencer(TLM通信,两个组件自带的seq_item_PORT)
assign_vi配置接口
//头文件`ifndef APB_MASTER_AGENT_SVH`define APB_MASTER_AGENT_SVHclass apb_master_agent extends uvm_agent;apb_config cfg;apb_master_driver driver;apb_master_sequencer sequencer;apb_master_monitor monitor;virtual apb_if vif;`uvm_component_utils_begin(apb_master_agent)// USER: Register your fields here`uvm_component_utils_end// new - constructorextern function new (string name, uvm_component parent);// uvm build phaseextern function void build();// uvm connection phaseextern function void connect();// This method assigns the virtual interfaces to the agent's childrenextern function void assign_vi(virtual apb_if vif);endclass : apb_master_agent`endif // APB_MASTER_AGENT_SVH//sv文件`ifndef APB_MASTER_AGENT_SV`define APB_MASTER_AGENT_SV//由于使用了头文件,这里的方法名都要加上apb_master_agent::function apb_master_agent::new(string name, uvm_component parent);super.new(name, parent);endfunction : newfunction void apb_master_agent::build();super.build();// get configif( !uvm_config_db#(apb_config)::get(this,"","cfg", cfg)) begin`uvm_warning("GETCFG","cannot get config object from config DB")cfg = apb_config::type_id::create("cfg");end// get virtual interfaceif( !uvm_config_db#(virtual apb_if)::get(this,"","vif", vif)) begin`uvm_fatal("GETVIF","cannot get vif handle from config DB")endmonitor = apb_master_monitor::type_id::create("monitor",this);monitor.cfg = cfg;if(cfg.is_active == UVM_ACTIVE) beginsequencer = apb_master_sequencer::type_id::create("sequencer",this);sequencer.cfg = cfg;driver = apb_master_driver::type_id::create("driver",this);driver.cfg = cfg;endendfunction : buildfunction void apb_master_agent::connect();assign_vi(vif);if(is_active == UVM_ACTIVE) begindriver.seq_item_port.connect(sequencer.seq_item_export); endendfunction : connectfunction void apb_master_agent::assign_vi(virtual apb_if vif);monitor.vif = vif;if (is_active == UVM_ACTIVE) beginsequencer.vif = vif; driver.vif = vif; endendfunction : assign_vi`endif // APB_MASTER_AGENT_SV
apb_master_driver
`ifndef APB_MASTER_DRIVER_SVH`define APB_MASTER_DRIVER_SVHclass apb_master_driver extends uvm_driver #(apb_transfer);apb_config cfg;`uvm_component_utils_begin(apb_master_driver)`uvm_component_utils_endextern function new (string name, uvm_component parent);extern virtual task run();virtual apb_if vif;extern virtual protected task get_and_drive();extern virtual protected task drive_transfer(apb_transfer t);extern virtual protected task reset_listener();extern protected task do_idle();extern protected task do_write(apb_transfer t);extern protected task do_read(apb_transfer t);endclass : apb_master_driver`endif // APB_MASTER_DRIVER_SVH
driver的run_phase同时运行两个线程:get and drive和复位。
而1.1就是要编写drive这个方法,对应APB的idle和读写,分别对应三个方法。在三个方法中根据时序图编写,addr和write在idle时保持不变(为了省电),时序图和代码如下。
`ifndef APB_MASTER_DRIVER_SV`define APB_MASTER_DRIVER_SVfunction apb_master_driver::new (string name, uvm_component parent);super.new(name, parent);endfunction : newtask apb_master_driver::run();forkget_and_drive();reset_listener();join_noneendtask : runtask apb_master_driver::get_and_drive();forever beginseq_item_port.get_next_item(req);`uvm_info(get_type_name(), "sequencer got next item", UVM_HIGH)drive_transfer(req);void'($cast(rsp, req.clone()));rsp.set_sequence_id(req.get_sequence_id());seq_item_port.item_done(rsp);`uvm_info(get_type_name(), "sequencer item_done_triggered", UVM_HIGH)endendtask : get_and_drivetask apb_master_driver::drive_transfer (apb_transfer t);`uvm_info(get_type_name(), "drive_transfer", UVM_HIGH)case(t.trans_kind)IDLE : this.do_idle();WRITE : this.do_write(t);READ : this.do_read(t);default : `uvm_error("ERRTYPE", "unrecognized transaction type")endcaseendtask : drive_transfertask apb_master_driver::do_write(apb_transfer t);`uvm_info(get_type_name(), "do_write ...", UVM_HIGH)@(vif.cb_mst);vif.cb_mst.paddr <= t.addr;vif.cb_mst.pwrite <= 1;vif.cb_mst.psel <= 1;vif.cb_mst.penable <= 0;vif.cb_mst.pwdata <= t.data;@(vif.cb_mst);vif.cb_mst.penable <= 1;repeat(t.idle_cycles) this.do_idle();endtask: do_writetask apb_master_driver::do_read(apb_transfer t);`uvm_info(get_type_name(), "do_write ...", UVM_HIGH)@(vif.cb_mst);vif.cb_mst.paddr <= t.addr;vif.cb_mst.pwrite <= 0;vif.cb_mst.psel <= 1;vif.cb_mst.penable <= 0;@(vif.cb_mst);vif.cb_mst.penable <= 1;#100ps;//为了采样,人为添加一个延迟,避免detal cyclet.data = vif.prdata;repeat(t.idle_cycles) this.do_idle();endtask: do_readtask apb_master_driver::do_idle();`uvm_info(get_type_name(), "do_idle ...", UVM_HIGH)@(vif.cb_mst);//vif.cb_mst.paddr <= 0;//vif.cb_mst.pwrite <= 0;vif.cb_mst.psel <= 0;vif.cb_mst.penable <= 0;vif.cb_mst.pwdata <= 0;endtask:do_idletask apb_master_driver::reset_listener();`uvm_info(get_type_name(), "reset_listener ...", UVM_HIGH)forkforever begin@(negedge vif.rstn); // ASYNC resetvif.paddr <= 0;vif.pwrite <= 0;vif.psel <= 0;vif.penable <= 0;vif.pwdata <= 0;endjoin_noneendtask`endif // APB_MASTER_DRIVER_SV
1.1实现apb_master_driver的drive_transfer()
task apb_master_driver::drive_transfer (apb_transfer t);`uvm_info(get_type_name(), "drive_transfer", UVM_HIGH)case(t.trans_kind)IDLE : this.do_idle();WRITE : this.do_write(t);READ : this.do_read(t);default : `uvm_error("ERRTYPE", "unrecognized transaction type")endcaseendtask : drive_transfer//其中apb_transfer如下,就是个item`ifndef APB_TRANSFER_SV`define APB_TRANSFER_SVtypedef enum {IDLE, WRITE, READ } apb_trans_kind;class apb_transfer extends uvm_sequence_item;rand bit [31:0]addr;rand bit [31:0]data;rand apb_trans_kind trans_kind; rand int idle_cycles;constraint cstr{soft idle_cycles == 1; };`uvm_object_utils_begin(apb_transfer)`uvm_field_enum(apb_trans_kind, trans_kind, UVM_ALL_ON)`uvm_field_int(addr, UVM_ALL_ON)`uvm_field_int(data, UVM_ALL_ON)`uvm_field_int(idle_cycles, UVM_ALL_ON)`uvm_object_utils_endfunction new (string name = "apb_transfer_inst");super.new(name);endfunction : newendclass : apb_transfer`endif // APB_TRANSFER_SV
再来看monitor
apb_master_monitor
TLM通信端口是uvm_analysis_port
`ifndef APB_MASTER_MONITOR_SVH`define APB_MASTER_MONITOR_SVHclass apb_master_monitor extends uvm_monitor;apb_config cfg;bit checks_enable = 1;bit coverage_enable = 1;virtual apb_if vif;uvm_analysis_port #(apb_transfer) item_collected_port;`uvm_component_utils_begin(apb_master_monitor)`uvm_field_int(checks_enable, UVM_ALL_ON)`uvm_field_int(coverage_enable, UVM_ALL_ON)`uvm_component_utils_endextern function new(string name, uvm_component parent=null);extern function void build();extern virtual task run();event apb_master_cov_transaction;covergroup apb_master_cov_trans @apb_master_cov_transaction;endgroup : apb_master_cov_transprotected apb_transfer trans_collected;extern virtual protected task monitor_transactions();extern virtual protected task collect_transfer();extern protected function void perform_transfer_checks();extern protected function void perform_transfer_coverage();endclass : apb_master_monitor`endif // APB_MASTER_MONITOR_SVH
`ifndef APB_MASTER_MONITOR_SV`define APB_MASTER_MONITOR_SVfunction apb_master_monitor::new(string name, uvm_component parent=null);super.new(name, parent);item_collected_port = new("item_collected_port",this);trans_collected = new();endfunction:new// buildfunction void apb_master_monitor::build();super.build();endfunction : build task apb_master_monitor::monitor_transactions();forever begin// Extract data from interface into transactioncollect_transfer();// Check transactionif (checks_enable)perform_transfer_checks();// Update coverageif (coverage_enable)perform_transfer_coverage();// Publish to subscribersitem_collected_port.write(trans_collected);endendtask // monitor_transactionstask apb_master_monitor::run();forkmonitor_transactions();join_noneendtask // runtask apb_master_monitor::collect_transfer();apb_transfer t;// Advance clock@(vif.cb_mon);if(vif.cb_slv.psel === 1'b1 && vif.cb_slv.penable === 1'b0) begint = apb_transfer::type_id::create("t");case(vif.cb_slv.pwrite)1'b1 : begin@(vif.cb_mon);//在第二个周期t.addr = vif.cb_mon.paddr;t.data = vif.cb_mon.pwdata;t.trans_kind = WRITE;end 1'b0 : begin@(vif.cb_mon);t.addr = vif.cb_mon.paddr;t.data = vif.cb_mon.prdata;t.trans_kind = READ;enddefault : `uvm_error(get_type_name(), "ERROR pwrite signal value")endcaseitem_collected_port.write(t);endendtask: collect_transfer // perform_transfer_checksfunction void apb_master_monitor::perform_transfer_checks();// USER: do some checks on the transfer hereendfunction : perform_transfer_checks// perform_transfer_coveragefunction void apb_master_monitor::perform_transfer_coverage();// USER: coverage implementation-> apb_master_cov_transaction;endfunction : perform_transfer_coverage`endif // APB_MASTER_MONITOR_SV
1.3编写apb_master_monitor中的collect_transfer()
用于从接口获取item过来。
在setup阶段判断是读还是写,注意!要多用一次@(vif.cb_mon),APB在第二个周期完成读写操作
然后把item通过analysis port的write函数写入item
task apb_master_monitor::collect_transfer();apb_transfer t;// Advance clock@(vif.cb_mon);if(vif.cb_slv.psel === 1'b1 && vif.cb_slv.penable === 1'b0) begint = apb_transfer::type_id::create("t");case(vif.cb_slv.pwrite)1'b1 : begin@(vif.cb_mon);//在第二个周期t.addr = vif.cb_mon.paddr;t.data = vif.cb_mon.pwdata;t.trans_kind = WRITE;end 1'b0 : begin@(vif.cb_mon);t.addr = vif.cb_mon.paddr;t.data = vif.cb_mon.prdata;t.trans_kind = READ;enddefault : `uvm_error(get_type_name(), "ERROR pwrite signal value")endcaseitem_collected_port.write(t);endendtask: collect_transfer
apb_master_sequence_Lib
包含base seq和各个seq,包括写1个,读1个,写完读1个,连续写,连续读,每次读写都需要一次`uvm_do_with,读完要把总线设为idle
1.2编写apb_master_sequence中的apb_master_write_seq::body()任务
base sequence只注册和例化,等待具体的子类来扩展。
一共有5个seq,如上所示。
single_write的body,发送一个req过去后获取一个rsp
virtual task body();`uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)`uvm_do_with(req, {trans_kind == WRITE; addr == local::addr; data == local::data;})get_response(rsp);`uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)endtask: body
single_read的body,发送req过去后获取rsp并将rsp的data给到data,完成读出
virtual task body();`uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)`uvm_do_with(req, {trans_kind == READ; addr == local::addr;})get_response(rsp);data = rsp.data;`uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)endtask: body
write_read的body,写的时候要限定类型和idle_cycle,拿到rsp后读,读只限定类型和地址即可
virtual task body();`uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)`uvm_do_with(req, {trans_kind == WRITE; addr == local::addr; data == local::data;idle_cycles == local::idle_cycles;})get_response(rsp);`uvm_do_with(req, {trans_kind == READ; addr == local::addr;})get_response(rsp);data = rsp.data;`uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)endtask: body
burst_write的body,写完拿到rsp后要将类型置为IDLE
不明白为什么地址要addr == local::addr + (i<<2);
virtual task body();`uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)foreach(data[i]) begin`uvm_do_with(req, {trans_kind == WRITE; addr == local::addr + (i<<2); data == local::data[i];idle_cycles == 0;})get_response(rsp);end`uvm_do_with(req, {trans_kind == IDLE;})get_response(rsp);`uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)endtask: body
burst_read的body同理。
virtual task body();`uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)foreach(data[i]) begin`uvm_do_with(req, {trans_kind == READ; addr == local::addr + (i<<2); idle_cycles == 0;})get_response(rsp);data[i] = rsp.data;end`uvm_do_with(req, {trans_kind == IDLE;})get_response(rsp);`uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)endtask: body
apb_master_sequencer
只有注册和new函数例化
slave端
agent是一样的
apb_slave_driver
由于要接收master端的item,这里有个关联数组mem。
之前的mem出现在test的base_test_sequence中,用来存放读写数据。
与master相对应的是drive req,这里是drive_response,这也作为一个并行线程(master是两个并行线程)
复位时要清空mem的数据,因为这边是从端
`ifndef APB_SLAVE_DRIVER_SV`define APB_SLAVE_DRIVER_SVfunction apb_slave_driver::new (string name, uvm_component parent);super.new(name, parent);endfunction : newtask apb_slave_driver::run();forkget_and_drive();reset_listener();drive_response();join_noneendtask : runtask apb_slave_driver::get_and_drive();forever beginseq_item_port.get_next_item(req);`uvm_info(get_type_name(), "sequencer got next item", UVM_HIGH)void'($cast(rsp, req.clone()));rsp.set_sequence_id(req.get_sequence_id());seq_item_port.item_done(rsp);`uvm_info(get_type_name(), "sequencer item_done_triggered", UVM_HIGH)endendtask : get_and_drivetask apb_slave_driver::drive_response();`uvm_info(get_type_name(), "drive_response", UVM_HIGH)forever begin@(vif.cb_slv);if(vif.cb_slv.psel === 1'b1 && vif.cb_slv.penable === 1'b0) begincase(vif.cb_slv.pwrite)1'b1 : this.do_write();1'b0 : this.do_read();default : `uvm_error(get_type_name(), "ERROR pwrite signal value")endcaseendelse beginthis.do_idle();endendendtask : drive_responsetask apb_slave_driver::reset_listener();`uvm_info(get_type_name(), "reset_listener ...", UVM_HIGH)forkforever begin@(negedge vif.rstn); // ASYNC resetvif.prdata <= 0;this.mem.delete(); // reset internal memoryendjoin_noneendtask: reset_listenertask apb_slave_driver::do_idle();`uvm_info(get_type_name(), "do_idle", UVM_HIGH)vif.cb_slv.prdata <= 0;endtask: do_idletask apb_slave_driver::do_write();bit[31:0] addr;bit[31:0] data;`uvm_info(get_type_name(), "do_write", UVM_HIGH)@(vif.cb_slv);addr = vif.cb_slv.paddr;data = vif.cb_slv.pwdata;mem[addr] = data;endtask: do_writetask apb_slave_driver::do_read();bit[31:0] addr;bit[31:0] data;`uvm_info(get_type_name(), "do_read", UVM_HIGH)wait(vif.penable === 1'b1);addr = vif.cb_slv.paddr;if(mem.exists(addr))data = mem[addr];elsedata = 0;#1ps;vif.prdata <= data;@(vif.cb_slv);endtask: do_read`endif // APB_SLAVE_DRIVER_SV
1.4编写apb_slave_driver的drive_response()
还是APB的状态机在setup判断是读还是写,对应读写函数。
task apb_slave_driver::drive_response();`uvm_info(get_type_name(), "drive_response", UVM_HIGH)forever begin@(vif.cb_slv);if(vif.cb_slv.psel === 1'b1 && vif.cb_slv.penable === 1'b0) begincase(vif.cb_slv.pwrite)1'b1 : this.do_write();1'b0 : this.do_read();default : `uvm_error(get_type_name(), "ERROR pwrite signal value")endcaseendelse beginthis.do_idle();endendendtask : drive_response
读写函数另外编写,按照时序图给出对应的信号。
task apb_slave_driver::do_idle();`uvm_info(get_type_name(), "do_idle", UVM_HIGH)vif.cb_slv.prdata <= 0;endtask: do_idletask apb_slave_driver::do_write();bit[31:0] addr;bit[31:0] data;`uvm_info(get_type_name(), "do_write", UVM_HIGH)@(vif.cb_slv);addr = vif.cb_slv.paddr;data = vif.cb_slv.pwdata;mem[addr] = data;endtask: do_writetask apb_slave_driver::do_read();bit[31:0] addr;bit[31:0] data;`uvm_info(get_type_name(), "do_read", UVM_HIGH)wait(vif.penable === 1'b1);addr = vif.cb_slv.paddr;if(mem.exists(addr))data = mem[addr];elsedata = 0;#1ps;vif.prdata <= data;@(vif.cb_slv);endtask: do_read
apb_slave_seq_lib
只是发送了一个item给driver,没法控制driver。
为什么要让sequence控制driver?
`ifndef APB_SLAVE_SEQ_LIB_SV`define APB_SLAVE_SEQ_LIB_SVclass example_apb_slave_seq extends uvm_sequence #(apb_transfer);function new(string name=""); super.new(name);endfunction : new`uvm_object_utils(example_apb_slave_seq) apb_transfer this_transfer;virtual task body();`uvm_info(get_type_name(),"Starting example sequence", UVM_HIGH)`uvm_do(this_transfer) `uvm_info(get_type_name(),$psprintf("Done example sequence: %s",this_transfer.convert2string()), UVM_HIGH)endtaskendclass : example_apb_slave_seq`endif // apb_SLAVE_SEQ_LIB_SV