多个sequence可以与同一个接口的driver并发交互。sequencer支持多种仲裁机制,以确保在任何时间点只有一个sequence可以访问driver。哪个sequence可以发送sequence_item取决于用户选择的仲裁机制。在UVM中实现了五种内置的仲裁机制。还有一个附加的回调函数可以实现用户定义的算法。sequencer具有一种称为set_arbitration()的方法,可以调用该方法来选择sequencer应使用哪种算法进行仲裁。可以选择的六种算法如下:
1.SEQ_ARB_FIFO(默认值)。如果指定了此仲裁模式,那么sequencer将使用一个FIFO选择sequence。例如:如果seq1,seq2和seq3在sequencer上运行,它将首先从seq1中选择一个item,然后从seq2中选择一个item,然后从seq3中选择一个item(如果可用),然后继续。2.SEQ_ARB_WEIGHTED:如果选择此仲裁模式,则始终首先选择优先级最高的sequence,直到没有可用为止,然后再选择下一个优先级sequence,依此类推。如果两个sequence具有相同的优先级,则以随机顺序从中选择它们。3.SEQ_ARB_RANDOM:如果选择此仲裁模式,忽略所有优先级以随机顺序选择sequence。4.SEQ_ARB_STRICT_FIFO:与SEQ_ARB_WEIGHTED相似,不同之处在于,如果两个sequence具有相同的优先级,则来自这些sequence的项顺序是按FIFO顺序而不是随机顺序选择的。5.SEQ_ARB_STRICT_RANDOM:与SEQ_ARB_RANDOM相似,只是不忽略优先级。首先从优先级最高的序列中随机选择sequence,然后依次选择下一个和最高顺序。6.SEQ_ARB_USER:此算法允许用户定义用于sequence之间仲裁的自定义算法。这是通过扩展uvm_sequencer类并覆盖user_priority_arbitration()方法来完成的。[282] 在sequencer上启动sequence时,如何指定其优先级?
通过将参数传递给序列的start()方法来指定优先级。根据为权重来确定优先级。例如:第三个参数指定sequence的优先级。
seq_1.start(m_sequencer, this, 500); //Highest priority
seq_2.start(m_sequencer, this, 300); //Next Highest priority
seq_3.start(m_sequencer, this, 100); //Lowest priority among three sequences
当在sequencer上运行多个sequence时,sequencer进行仲裁并授予sequence的访问权限。有时,一个sequence可能希望独占sequencer的访问权限,直到将其所有sequence_item送到driver为止(例如:如果您希望在不中断的情况下激发确定性模式)。有两种机制允许sequence获得对sequencer的独占访问权。
1.使用lock()和unlock():sequence可以调用在其上运行的sequencer的lock方法。当sequence通过sequencer仲裁机制获得下一个访问权限时,将授予该sequence对driver的独占访问权限。如果还有其他标记为更高优先级的sequence,则此序列需要等待,直到获得授权。权限锁定后,其他sequence将无法访问driver,直到该序列在sequencer上调用unlock()后,权限将被释放。lock方法是一个阻塞方法,直到授予锁后才返回。2.使用grab()和ungrab():grab方法类似于lock方法,可以在需要独占访问时由sequence调用。grab和lock之间的区别在于,调用grab()时,它将立即生效,并且该sequence将100%获得下一个sequencer的授权机会,从而高于所有当前的sequence优先级。阻止sequence通过grab独占sequencer的唯一方法是sequencer上已经存在的lock()或grab()。
在sequencer上运行的sequence使用sequencer的grab()和lock()方法来获得对该序列器的独占访问权,直到调用了相应的unlock()或ungrab()。grab和lock之间的区别在于,当调用sequencer上的某个grab()时,它将立即生效,并且该sequence将获得下一个sequencer的授权,从而高于所有当前的sequence优先级。但是,对lock()的调用将需要等待,直到sequence根据设置的优先级和仲裁机制获得其下一个授权为止。在用法方面,可以使用lock来对优先级中断进行建模,并使用grab来对不可屏蔽的中断进行建模,在别的建模场景中也很有用。
根据需要如何通过interface发送激励,在UVM driver类中可以实现两种模式。
1.非流水线模型:如果driver一次仅对一个事务进行建模,则称为非流水线模型。在这种情况下,sequence可以将一个事务发送给driver,并且driver可能需要几个周期(基于接口协议)才能完成驱动该事务。只有在那之后,驱动程序才会接受sequencer的新事务.
class nonpipe_driver extends uvm_driver #(req_c);
task run_phase(uvm_phase phase);
req_c req;
forever begin
get_next_item(req); // Item from sequence via sequencer
// drive request to DUT which can take more clocks
// Sequence is blocked to send new items til then
item_done(); // ** Unblocks finish_item() in sequence
end
endtask: run_phase
endclass: nonpipe_driver
1.流水线模型:如果driver一次建模多个活动事务,则称为流水线模型。在这种情况下,sequence可以继续向driver发送新事务,而无需等待driver完成之前的事务。在这种情况下,对于从该sequence发送的每个事务,driver都会派生一个单独的进程来基于该事务驱动接口信号,不会等到它完成后再接受新事务。如果我们要在接口上回送请求而不等待设计的响应,应该采用这种建模。
class pipeline_driver extends uvm_driver #(req_c);
task run_phase(uvm_phase phase);
req_c req;
forever begin
get_next_item(req); // Item from sequence via sequencer
fork begin //drive request to DUT which can take more clocks
//separate thread that doesn't block sequence
//driver can accept more items without waiting
end
join_none
item_done(); // ** Unblocks finish_item() in sequence
end
endtask: run_phase
endclass: pipeline_driver
如果从driver返回了几个序列之一的响应,则sequencer将sequence中的sequenceID字段用于将响应路由回正确的sequence。driver中的响应处理代码应调用set_id_info(),以确保任何响应项都具有与其原始请求相同的sequenceID。下面是driver中的一个示例代码,该代码获取sequencer_item的id并发送回响应(请注意,这是用于说明的参考伪代码,并且假定某些功能在其他地方进行了编码)
class my_driver extends uvm_driver;
//function that gets item from sequence port and
//drives response back
function drive_and_send_response();
forever begin
seq_item_port.get(req_item);
//function that takes req_item and drives pins
drive_req(req_item);
//create a new response
itemrsp_item = new();
//some function that monitors response signals from dut
rsp_item.data = m_vif.get_data();
//copy id from req back to response
rsp.set_id_info(req_item);
//write response on rsp port
rsp_port.write(rsp_item);
end
endfunction
endclass
启动sequence时,它始终与启动sequencer相关联。m_sequencer句柄指向了当前sequence挂载的sequencer。使用此句柄,序列可以访问UVM组件层次结构中的任何信息和其他资源句柄。
UVM sequence是寿命有限的对象,与sequencer,driver或monitor不同,后者是UVM的组件,并且在整个仿真时间内都存在。因此,如果需要从测试平台层次结构(组件层次结构)访问任何成员或句柄,sequence需要sequencer的句柄。m_sequencer是uvm_sequencer_base类型的句柄,默认情况下在uvm_sequence中可用。但是,要访问在其上运行sequence的真实sequencer,我们需要将m_sequencer转换为真实sequencer,通常称为p_sequencer(可以自定义为任何名字,不仅仅是p_sequencer)。下面是一个简单的示例,其中sequence要访问clock monitor组件的句柄,该组件可在sequencer中用作句柄。
//clock monitor component which is available as a handle in the sequencer.
class test_sequence_c extends uvm_sequence;
test_sequencer_c p_sequencer;
clock_monitor_c my_clock_monitor;
task pre_body();
if(!$cast(p_sequencer, m_sequencer))begin
`uvm_fatal("Sequencer Type Mismatch:", " Wrong Sequencer");
end
my_clock_monitor = p_sequencer.clk_monitor;
endtask
endclass
class test_Sequencer_c extends uvm_sequencer;
clock_monitor_c clk_monitor;
endclass
在早期随机化中,首先使用randomize()对sequence进行随机化,然后使用start_item()来请求对sequencer的访问,这是一个阻塞调用,根据sequencer的繁忙程度可能会花费一些时间。下面的示例显示一个对象(req)首先被随机化,然后sequence等待仲裁。
task body()
assert(req.randomize());
start_item(req); //Can consume time based on sequencer arbitration
finish_item(req);
endtask
在后期随机化中,sequence首先调用start_item(),等待直到sequencer批准仲裁,然后在将事务发送给sequencer/driver之前,调用randomize。这样做的好处是,可以将item及时地随机化,并且可以在将item发送给driver之前使用来自设计或其他组件的任何反馈。以下代码中使用的就是后期随机化(req)
task body()
start_item(req); //Can consume time based on sequencer arbitration
assert(req.randomize());
finish_item(req);
endtask
subsequence是从另一个sequence开始的sequence。从sequence的body()中,如果调用了另一个sequence的start(),则通常将其称为subsequence。
get_next_item是一个阻塞方法(driver-sequencer API的一部分),阻塞直到sequence_item可供driver处理为止,并返回指向sequence_item的指针。try_next_item是非阻塞版本,如果没有sequence_item可用于driver处理,则它将返回空指针。
get_next_item是一个阻塞调用,用于从sequencer FIFO获取sequence_item以供driver处理。driver处理完sequence_item后,需要先使用item_done完成握手,然后再使用get_next_item()请求新的sequence_item。
get()也是一个阻塞调用,它从sequencer FIFO获取sequence_item以供driver处理。但是,在使用get()时,由于get()方法隐式完成了握手,因此无需显式调用item_done()。
driver的get()方法是一个阻塞调用,从sequencer FIFO获取sequence_item以供driver处理。一旦有sequence_item可用,它就会解锁,并与sequencer完成握手。peek()方法类似于get(),并阻塞直到sequence_item可用为止。但是,它不会从sequencer FIFO中删除sequence。因此,多次调用peek()将在driver中返回相同的sequence_item。
item_done()方法是driver中的一种非阻塞方法,用于在get_next_item()或try_next_item()成功之后与sequencer完成握手。如果不需要发回响应,则不带参数调用item_done(),它将完成握手,而无需在sequencer响应FIFO中放置任何内容。如果希望将响应发送回去,则将item_done()与指向响应sequence_item的句柄作为参数一起传递。该响应句柄将放置在sequencer响应FIFO中,sequence通过这种方式进行响应。
1.get()2.get_next_item()3.item_done()4.put()5.try_next_item()6.peek()
get(), get_next_item(), peek() 是阻塞的
try_next_item(), item_done(), and put() 是非阻塞的
1)
function get_drive_req();
forever begin
req = get();
req = get();
end
endfunction
2)
function get_drive_req();
forever begin
req = get_next_item();
req = get_next_item();
item_done();
end
endfunction
3)
function get_drive_req();
forever begin
req = peek();
req = peek();
item_done();
req = get();
end
endfunction
2是错的,因为不能在调用item_done之前两次调用get_next_item,它无法完成与sequencer的握手。
sequencer具有stop_sequences()方法,可用于停止所有sequence。但是,此方法不检查driver当前是否正在处理任何sequence_items。因此,如果driver调用item_done()或put(),则可能会出现致命错误,因为sequence指针可能无效。因此,用户需要注意,一旦调用stop_sequence(),就禁用了sequencer线程(如果在fork中启动)。
convert2string():建议实现此函数,该函数返回对象的字符串表示形式(其数据成员的值)。这对于将调试信息打印到模拟器脚本或日志文件很有用。
task body();
seq_item_c req;
start_item(req);
#10 ns;
assert(req.randomize());
finish_item(req);
endtask
应该避免在start_item和finish_item之间添加延迟。start_item返回后,该sequence将赢得仲裁并可以访问driver-sequencer。从那时起直到finish_item的任何延迟都将阻塞driver-sequencer,并且使得任何其他sequence都不能访问driver-sequencer。如果在一个接口上运行多个sequence,并且延迟很大,设计接口上有很多空闲时,这会造成很大的麻烦。