UVM TLM通信
1 uvm put/get 通信
在uvm中,一对一的TLM端口有很多种类型,这里仅以最常见的场景:sequencer <-> driver的通信为例说明;需要事先说明的是,driver以forever的形式实现,因此只要driver能从sequencer get到transaction,就会一直发;
由于driver是请求端,所以在driver一侧例化了下面两种端口(已经在uvm_driver中申明例化,用户可以直接使用):
uvm_seq_item_pull_port #(REQ,RSP) seq_item_port;
uvm_analysis_port #(RSP) rsp_port;
而sequencer是响应端,所以在sequencer一侧例化了对应的两种端口(也已经在uvm_driver中申明例化,用户可以直接使用):
uvm_seq_item_pull_imp #(REQ,,RSP,this_type) seq_item_export;
uvm_analysis_export #(RSP) rsp_export;
但是driver的port和sequencer的export的连接需要由用户完成,因为uvm事先并不知道driver和sequencer的实例名:driver::seq_item_port.connect(sequencer::seq_item_export);
在sequencer这边,uvm_sequencer提供了多种方法(不止put()/get()),供driver使用,在driver中只需通过seq_item_port.mthod(...)的方式即可直接使用,
2 uvm analysis port
除了在sequencer与driver之间的通信是一对一的,在monitor,checker,reference model,scorboard之间的通信往往是一对多,因此在这些组件之间通行一般使用uvm analysis port;analysis port采用observer pattern来实现;
举例如下:
背景是sub_env_master中的mst_monitor在interface上监测到transaction后,将其以observer pattern的形式发送给mst_checker,同时也发给sub_env_slave。
class sub_env_matser extends uvm_env; // variable define. uvm_analysis_port#(dram_intf_trans) dram_data_ap; // 申明dram_data_ap function void build_phase(uvm_phase phase); super.build_phase(phase); ...... dram_data_ap = new("dram_data_ap", this); // 例化dram_data_ap mst_monitor = sub_monitor_master::type_id::create("mst_monitor", this); mst_checker = sub_checker_master::type_id::create("mst_checker", this); mst_monitor.dram_data_ap = dram_data_ap; // 将例化的dram_data_ap的句柄赋值给mst_monitor ...... endfunction function void connect_phase(uvm_phase phase); super.connect_phase(phase); ...... dram_data_ap.connect(mst_checker.dram_rdwr_data_imp); // 将dram_data_ap与mst_checker中的dram_rdwr_data_imp相连 ...... endfunction endclass
在sub_env_master中,需要做的事情是:
(1):申明dram_data_ap;
(2):在build_phase中例化dram_data_ap;
(3):由于是sub_env_master中的mst_monitor发送事务,而不是sub_env_master,因此需要将例化的dram_data_ap的句柄赋值给mst_monitor;
(4):在sub_env_master内部,需要mst_monitor需要将transaction传给mst_checker,因此需要将dram_data_ap与mst_checker中的dram_rdwr_data_imp相连;当然,在mst_checker中必定有function write_dram_rdwr_data(...)去接受transaction;
需要思考的是,明明是mst_monitor发送transaction,为什么要在sub_env_master中例化dram_data_ap,再赋值给mst_monitor?为什么不直接在mst_monitor中去例化dram_data_ap呢?
这是因为在top_env中,只会例化其下一层的组件,即sub_env_master和sub_env_slave;至于sub_env_master/sub_env_slave会不会例化其自己的agent/monitor/driver等组件,由sub_env_master/sub_env_slave自己决定,top_env并不知道;因此在top_env这层,只会将sub_env_master的ap和sub_env_slave的imp相连:sub_env_mst.dram_data_ap.connect(sub_env_slv.dram_wr_data_export),不会将sub_env_master的子组件与sub_env_slave相连。
因此作为transaction发送的起点,在mst_monitor的run_phase中,会通过dram_data_ap.write(m_trans)的方式将m_trans发送到与dram_data_ap相连接的imp端;
`uvm_analysis_imp_decl(_dram_wr_data) // 宏申明不同的imp方法 `uvm_analysis_imp_decl(_dram_rd_data) class sub_env_slave extends uvm_env; //variable define. uvm_analysis_imp_dram_wr_data #(dram_intf_trans, sub_env_slave) dram_wr_data_export; uvm_analysis_imp_dram_rd_data #(dram_intf_trans, sub_env_slave) dram_rd_data_export; function void build_phase(uvm_phase phase); super.build_phase(phase); ...... dram_wr_data_export = new("dram_wr_data_export", this); // 申明并例化dram_wr_data_export dram_rd_data_export = new("dram_rd_data_export", this); // 申明并例化dram_rd_data_export ...... endfunction function void write_dram_wr_data(dram_intf_trans trans); // 用来接收transaction的imp方法的实现 // process write trans... endfunction function void write_dram_rd_data(dram_intf_trans trans); // 用来接收transaction的imp方法的实现 // process read trans... endfunction endclass
在sub_env_slave中,需要做的事情是:
(1):由于在sub_env_slave中需要实现多个write方法,因此需要用宏`uvm_analysis_imp_decl(...)区分出不同类型的imp,然后分别用这些类型的imp去声明export;至于为什么叫dram_wr_data_export而不是叫dram_wr_data_imp?是因为m_trans传到write_dram_wr_data这里可能还不是终点,也许dram_wr_data_export还会作为producer继续将m_trans发送到下一个组件中,因此将其命名成dram_wr_data_imp并不合理;当然,这个名字可以随意命名,无需与`uvm_analysis_imp_decl(...)中的申明保持一致;
(2):在build_phase中例化dram_wr_data_export和dram_rd_data_export,
(3):write_dram_wr_data()和write_dram_rd_data()方法的实现;
需要说明的是,一般不直接在sub_env_slave中实现imp方法,而是在sub_env_slave的子组件中实现,这里只是为了阐述方便在sub_env_slave中实现;如果要在sub_env_slave的子组件slv_checker中实现,需要做的是:在slv_checker中申明并创建dram_wr_data_imp和dram_rd_data_imp,并且在sub_env_slave的connect_phase中将dram_wr_data_export与dram_wr_data_imp相连,将dram_rd_data_export与dram_rd_data_imp相连,如下:
dram_wr_data_export.connect(slv_checker.dram_wr_data_imp);
dram_rd_data_export.connect(slv_checker.dram_rd_data_imp);
class top_env extends uvm_env; ...... function viod build_phase(uvm_phase phase); ...... sub_env_mst = sub_env_master::type_id::create("sub_env_mst", this); sub_env_slv = sub_env_slave::type_id::create("sub_env_slv", this); ...... endfunction function void connect_phase(uvm_phase phase); ...... sub_env_mst.dram_data_ap.connect(sub_env_slv.dram_wr_data_export); sub_env_mst.dram_data_ap.connect(sub_env_slv.dram_rd_data_export); ...... endfunction ...... endclass
在top_env中,需要做的事情是:
将sub_env_mst的connect_phase中的dram_data_ap与sub_env_slv中的dram_wr_data_export和dram_rd_data_export相连接,如上所述,只连接sub_env_mst和sub_env_slv这一层次的analysis port,不要连接到sub_env_mst和sub_env_slv的子组件中去,如果连接的子组件没有创建对象,VCS会报No Object Access。
3 同步通信原件