在平臺的搭建中,需要關注的重點一是平臺的結構,怎麼樣便於複用,怎麼樣便於使用,不需要知道內部層次結構就可以很好的配置和使用。另一點就是實現了,驅動怎麼實現?monitor怎麼實現?sequence怎麼實現?這些在VIP裏都是加密的,都是“核心機密”。而其他結構agent, coverge collector, scoreboard等等結構基本大同小異。本文主要討論“實現”這一方面的一小部分內容,給大家一點參考,有不正確的地方請不吝指正。
(轉載請註明出處,謝謝!seabeam)
按照mentor的分類方法,driver的model分爲
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起來可不是那麼容易啊。