Sequencer
sequence和它们的目标driver之间的req和rsp item的传输是通过在sequencer中实现的双向 TLM 通信机制来实现的。uvm_driver 类包含一个 uvm_seq_item_pull_port,它和sequencer中的 uvm_seq_item_pull_export。port和export类是sequence_items 类型参数化的。
一旦建立了port和export连接,driver代码就可以使用export中实现的 API 从sequence中获取请求 sequence_items 并将rsp返回给它们。
上图是连接关系,下面是对应的代码示例
// Driver parameterized with the same sequence_item for request & response
// response defaults to request
class adpcm_driver extends uvm_driver #(adpcm_seq_item);
....
endclass: adpcm_driver
// Agent containing a driver and a sequencer - uninteresting bits left out
class adpcm_agent extends uvm_agent;
adpcm_driver m_driver;
adpcm_agent_config m_cfg;
// uvm_sequencer parameterized with the adpcm_seq_item for request & response
uvm_sequencer #(adpcm_seq_item) m_sequencer;
// Sequencer-Driver connection:
function void connect_phase(uvm_phase phase);
if(m_cfg.active == UVM_ACTIVE) begin // The agent is actively driving stimulus
m_driver.seq_item_port.connect(m_sequencer.seq_item_export); // TLM connection
m_driver.vif = cfg.vif; // Virtual interface assignment
end
endfunction: connect_phase
driver和sequencer之间的连接是一对一的,不会出现多对一也不会出现一对多。除了标准端口,driver里还有一个analysis_port,可以连接sequencer中的一个analysis_export,实现driver和sequencer之间的单向响应通信路径。不过这是一个历史遗留组件,不常用,要使用这个口的话,下面是示例代码。
// Same agent as in the previous bidirectional example:
class adpcm_agent extends uvm_agent;
adpcm_driver m_driver;
uvm_sequencer #(adpcm_seq_item) m_sequencer;
adpcm_agent_config m_cfg;
// Connect method:
function void connect_phase(uvm_phase phase );
if(m_cfg.active == UVM_ACTIVE) begin
m_driver.seq_item_port.connect(m_sequencer.seq_item_export); //Always need this
m_driver.rsp_port.connect(m_sequencer.rsp_export); // Response analysis port connection
m_driver.vif = cfg.vif;
end
//...
endfunction: connect_phase
endclass: adpcm_agent
如果要在返回rsp的时候通知其他组件,可以用这个口,否则不要用。
Driver-Sequence API
uvm_driver 是 uvm_component 类的扩展,它添加了一个 uvm_seq_item_pull_port,来和sequence通信。uvm_driver 是一个参数化类,它被参数化为req sequence_item 的类型和rsp sequence_item 的类型。进一步地,这些参数用于参数化 uvm_seq_item_pull_port。rsp sequence_item 可以独立参数化的事实意味着driver可以从req类型返回不同的item类型。实际上,大多数driver对req和rsp使用相同的item,因此在源代码中,rsp item类型默认为req item类型。
uvm_driver 类的行为模型是它使用握手通信机制从sequencer req FIFO 中获取sequence_items,并将rsp sequence_items 返回到sequencer rsp FIFO(也可以不返回)。uvm_driver 中 seq_item_pull_port 的句柄是 seq_item_port类型。driver代码用来与sequencer交互的 API 被 seq_item_port 引用,但实际上是在sequencer seq_item_export 中实现的(这是标准的 TLM )。
说人话就是握手,driver收了可以不发rsp,不过这样sequence就就没有相关信息了。然后由于本质上还是通过tlm进行的,所有通过端口调用的函数都是在目标端口实现的,也就是说是在sequencer里实现的。
UVM Driver API
相关API包括:
get_next_item
这是一个阻塞方法,直到sequence用item_done方法把item发过来。如果在item_done调用之前,进行另一次get_next_item调用会导致握手死锁。
try_next_item
这是 get_next_item() 方法的非阻塞变体。sequencer的item fifo里没东西就会返回一个空句柄,如果有的话,行为就和get_next_item一样
item_done
是一个非阻塞方法,应该在get_next_item() 或成功的 try_next_item() 调用之后调用。如果不传参数或者传一个空句柄,也可以完成握手,然后sequencer的fifo也不会有item进去,如果传的不是空句柄的话,就会送进对应的fifo。
peek
如果在sequencer的对应 FIFO 中没有可用的 REQ sequence_item,peek() 方法将阻塞一直等待,然后返回一个指向 REQ 对象的指针,执行握手的前半部分。在 get() 或 item_done() 调用之前调用peek的话,返回的都是同一个对象。
很好理解,peek本质就是不会影响fifo的内容,get和item_done一个取走item,一个送入item,自然会产生影响。
get
和上面的peek对应,会影响fifo
put
这个是一个非阻塞的方法,可以在任何时候调用,和握手机制没有关系。
注意:get_next_item()、get() 和 peek() 方法会启动sequencer的仲裁,会从获得权限的sequence那里返回一个 sequence_item。
推荐的Driver和sequencer的api调用模型
对于driver和sequencer之间的item交互,推荐两种方式
get_next_item() 后跟 item_done()
通过get_next_item,拿到item以后,进行处理,最后再使用item_done完成握手。在 item_done() 调用中不应传递任何参数。
这种握手流程是最推荐的,因为他很明确的分离了driver和sequencer之间的任务。
//
// Driver run method
//
task run_phase( uvm_phase phase );
bus_seq_item req_item;
forever begin
seq_item_port.get_next_item(req_item); // Blocking call returning the next transaction
//BFM handles all pin wiggling and population of req_item with response data
m_bfm.drive(req_item);
seq_item_port.item_done(); // Signal to the sequence that the driver has finished with the item
end
endtask: run
相应的sequence代码里是 start_item() 后跟一个 finish_item()。由于driver和sequence中的sequence item指向的是同一个对象,因此可以通过item句柄在sequence内使用从driver返回的任何数据。换句话说,当一个 sequence_item 的句柄作为参数传递给 finish_item() 方法时,driver的 get_next_item() 方法拿到的 sequence_item 所指向的对象是同一个。当driver对 sequence_item 进行任何更改时,sequence也能看到item中的所有修改。driver对 item_done() 的调用会解除对sequence中的 finish_item() 调用的阻塞,然后sequence可以访问 sequence_item 中的字段,包括driver对item修改的那些字段。
//
// Sequence body method:
//
task body();
bus_seq_item req_item;
bus_seq_item req_item_c;
req_item = bus_seq_item::type_id::create("req_item");
repeat(10) begin
$cast(req_item_c, req_item.clone); // Good practice to clone the req_item item
start_item(req_item_c);
req_item_c.randomize();
finish_item(req_item_c); // Driver has returned REQ with the response fields updated
`uvm_info("body", req_item_c.convert2string())
end
endtask: body
get(req) 之后 put(rsp)
这种流程下,driver通过 get(req) 获取下一个 sequence_item 并在driver消耗时间处理 sequence_item 之前一次性发送回sequence的握手。driver使用 put(rsp) 方法来告诉sequence, sequence_item 已被传送出去。driver使用响应的建议在下一个章节中会提及。
使用这种flow,则sequence在 finish_item() 后面调用get_response() ,会阻塞直到驱动程序调用 put(rsp)。这种使用flow的缺点是在driver实现起来更复杂,并且sequence一定要记得处理rsp。
//
// run method within the driver
//
task run_phase( uvm_phase phase );
REQ req_item; //REQ is parameterized type for requests
RSP rsp_item; //RSP is parameterized type for responses
bit [15:0] rdata;
bit error;
forever begin
seq_item_port.get(req_item); // finish_item in sequence is unblocked
//BFM handles all pin wiggling and returns response data as separate arguments
m_bfm.drive(req_item, rdata, error);
$cast(rsp_item, req_item.clone()); // Create a response transaction by cloning req_item
rsp_item.set_id_info(req_item); // Set the rsp_item sequence id to match req_item
if(req_item.read_or_write == READ) begin // Copy the bus data to the response fields
rsp_item.read_data = rdata;
end
rsp_item.resp = error;
seq_item_port.put(rsp_item); // Handshake back to the sequence via its get_response() call
end
endtask
//
// Corresponding code within the sequence body method
//
task body();
REQ req_item; //REQ is parameterized type for requests
RSP rsp_item; //RSP is parameterized type for responses
repeat(10) begin
req_item = bus_seq_item::type_id::create("req_item");
start_item(req_item);
req_item.randomize();
finish_item(req_item); // This passes to the driver get() call and is returned immediately
get_response(rsp_item); // Block until a response is received
`uvm_info("body", rsp_item.convert2string(), UVM_LOW);
end
endtask: body
将respond item路由回到parent sequence中
当有多个sequence通过sequencer与driver通信时,模型的复杂度就会提高。sequencer负责将哪个 sequence_item 从哪个sequence路由到driver中。而当driver创建响应 sequence_item 之后,需要将其路由回正确的sequence。UVM 处理这个问题的方式是每个 sequence_item 都有一对 id 字段,一个用于parent sequence,一个用于标识 sequence_item,sequencer 使用这些字段将响应路由回parent sequence。作为 start_item() 方法的结果,请求 sequence_item 具有由sequencer设置的这些字段,因此,新的响应 sequence_item 需要获取请求 ID 信息的副本,以便可以将其路由回原始sequence。为此,在 uvm_sequence_item 基类中提供了 set_id_info() 方法。
意思就是返回rsp的时候,把id要复制一下,以便sequencer对于item的正确路由
Driver Response Items 的编码指南
set_id_info
uvm_sequence_item 有一个 id 字段,该字段由sequencer在sequence调用 start_item() 的时候设置。此 id 帮助sequencer跟踪每个 sequence_item 关联的sequence,并且此信息用于将响应item路由回正确的sequence。尽管在大多数情况下只有一个sequence与driver主动通信,但这个机制是一直运行的。sequence_item set_id_info 方法用于把请求item的id复制给响应id。返回响应item的时候,必须设置id。
如果通过克隆产生或者创建新的响应item,就必须要调用set_id_info给item设置对应的id。
和前面一段的意思是一样的
使用指针克隆保证安全
当一个响应item从driver发送回一个sequence时,它的指针将存储在sequencer的响应 FIFO 中。如果在发送下一个响应item指针之前没有使用响应item,除非新的响应item指针是针对新对象的,否则两个指针都将引用同一个对象。此问题的一个常见结果是连续读取 FIFO 会产生具有相同内容的对象。
防止这种情况发生的方法,推荐克隆响应item,以便创建一个新对象并将指向该新对象的指针传递给sequencer响应 FIFO 或具有不同的请求和响应类型。
// Somewhere in a driver - request and response items
bus_seq_item req_item;
bus_seq_item rsp_item;
task run_phase( uvm_phase phase );
forever begin
seq_item_port.get(req_item);
assert($cast(rsp_item, req_item.clone()); // This does not copy the id info
rsp_item.set_id_info(req_item); // This sets the rsp_item id to thereq_item id
//
// Do the pin level transaction, populate the response fields
//
// Return the response:
seq_item_port.put(rsp_item);
//
end
endtask: run