在平台的搭建中,需要关注的重点一是平台的结构,怎么样便于复用,怎么样便于使用,不需要知道内部层次结构就可以很好的配置和使用。另一点就是实现了,驱动怎么实现?monitor怎么实现?sequence怎么实现?这些在VIP里都是加密的,都是“核心机密”。而其他结构agent,coverge collector,scoreboard等等结构基本大同小异。本文主要讨论“实现”这一方面的一小部分内容,给大家一点参考,有不正确的地方请不吝指正。
(转载请注明出处,谢谢!seabeam)
Unidirectional Non-Pipelined: ADPCM,PCM
Bidirectional Non-Pipelined: AMBA APB
Pipelined: AMBA AHB
Out Of Order Pipelined: AMBA AXI,OCP
前两种相对简单,双向的无非加一个读写判断就搞定了,后两种的实现就比较麻烦,对于新手来说是个不小的挑战。
下面是mentor的例子:
class mbus_pipelined_driver extends uvm_driver #(mbus_seq_item); ... task run_phase(uvm_phase phase); @(posedge MBUS.MRESETN); @(posedge MBUS.MCLK); fork do_pipelined_transfer; do_pipelined_transfer; join endtask ...run_phase里面有两个并行线程,有几个phase,那么就应该有几个并行线程。但是两条并行的线程,同时去取tansaction然后一起驱动岂不是糟糕了?既然是流水线,那么第一个stage只应该驱动transaction 1的第一个phase,而第二个stage则应当同时驱动tansaction 1的第二个phase与transaction 2的第一个phase.如果你打算就这样驱动或者transaction做一些处理,把他们合并起来直接发送,根本不用并行操作,那么请左键右上角的x.
继续前文,关键问题在于两个相同的线程,怎么样让后一个线程在前一个线程的特定时间后延迟一段特定的时间再驱动。没错,do_pipelined_transfer()就是核心中的核心了,看一看怎么实现的:
task automatic do_pipelined_transfer; mbus_seq_item req; forever begin pipeline_lock.get(); seq_item_port.get(req); accept_tr(req,$time); void'(begin_tr(req,"pipelined_driver")); MBUS.MADDR <= req.MADDR; MBUS.MREAD <= req.MREAD; MBUS.MOPCODE <= req.MOPCODE; @(posedge MBUS.MCLK); while(!MBUS.MRDY == 1) begin @(posedge MBUS.MCLK); end // End of command phase: // - unlock pipeline semaphore pipeline_lock.put(); // Complete the data phase if(req.MREAD == 1) begin @(posedge MBUS.MCLK); while(MBUS.MRDY != 1) begin @(posedge MBUS.MCLK); end req.MRESP = MBUS.MRESP; req.MRDATA = MBUS.MRDATA; end else begin MBUS.MWDATA <= req.MWDATA; @(posedge MBUS.MCLK); while(MBUS.MRDY != 1) begin @(posedge MBUS.MCLK); end req.MRESP = MBUS.MRESP; end // Return the request as a response seq_item_port.put(req); end_tr(req); end endtask: do_pipelined_transfer
红色高亮就是关键所在,延时全靠它了。pipeline_lock是一个SV的内建数据类型信号量( semaphore),正是它保证了两个线程的pipeline操作。当第一个线程的第一个phase开始时,信号量锁住,第二个线程需要等待信号量的释放。当第一个线程把第一个phase驱动完毕后,释放信号量,这时候第一个线程开始驱动其二个phase,而第二个线程也可以取数开始驱动第一个phase了。
如果有更多的phase怎么办?mentor没有提及,其实原理是类似的,有n级流水线,那么run_phase()里就应该开启多条并行线程,但是do_pipeline_transfer()里还是一样,只锁住第一个stage即可。sequence就只管不停的发数,driver只管来一个transaction驱动一个,所以是forever无线循环。
这种方法的好处是直观,很符合通常的印象,但是并行线程debug起来可不是那么容易啊。