天天看點

晶片漫遊指南(4) -- UVM序列

目錄

    • 1 新手上路
      • 1.1 概述
      • 1.2 序列元件的互動
      • 1.3 繼承關系
      • 1.4 提示
      • 1.5 總結
    • 2 Sequence和item
      • 2.1 概述
      • 2.2 Sequence和Item示例
      • 2.3 item與sequence的關系
      • 2.4 Flat Sequence介紹
      • 2.5 示例一
      • 2.6 示例2
      • 2.7 Hierarchical Sequence介紹
    • 3 Sequencer和Driver
      • 3.1 概述
      • 3.2事務傳輸執行個體
      • 3.3 通信時序
    • 4. Sequencer和Sequence
      • 4.1概述
      • 4.2 sequence和item發送執行個體
      • 4.3 發送序列的相關宏
      • 4.4 sequencer的仲裁特性介紹
      • 4.5 Sequencer的仲裁示例
      • 4.6 uvm_sequencer鎖定機制
      • 4.7 sequence的階層化

1 新手上路

1.1 概述

  • 在UVM世界,利用其核心特性,在建立了元件和頂層環境,并且完成元件之間的TLM端口連接配接之後,接下來就可以使得整個環境開始運轉了。
  • 在經過一番時間,掌握了元件之間的TLM通信方式,開辟了建築之間的道路,橋梁和河道以後,就可以進入緊張繁忙的物流期了。
  • 運轉的必要條件是元件之間需要有事務(transaction)傳送,這就同管道連接配接好需要水流一樣。
  • 如果城市沒有交通,那麼顯然不會有多熱鬧。
  • 在本章我們将主要圍繞下面幾個核心詞來闡述它們的作用、分類以及之間的互動關系:
    • sequence item
    • sequence
    • sequencer
    • driver
  • 如果按照交通道路的車流來打比方,sequence就是道路,sequence item是道路上行駛的貨車,sequencer是目的地的關卡,而driver便是最終卸貨的地方。
  • 從軟體層面來講,這裡的貨車是從sequence一端出發的,經過了sequencer,最終抵達driver。
  • 經過driver的卸貨,每一輛貨車也就完成了它的使命。
  • driver對每一件到站的貨物,經過掃描處理,将它們分解為更小的資訊量,提供給DUT。

1.2 序列元件的互動

晶片漫遊指南(4) -- UVM序列
  • 在這個過程中,不同 的角色之間會進行下面的互動:
  • sequence對象會産生目标數量的sequence item對象。借助于SV的随機化和sequence item對随機化的支援,使得産生的每個sequence item對象中的資料内容都不相同。
  • 産生的sequence item會經過sequencer再流向driver。
  • dirver陸續得到每一個sequence item,經過資料解析,将資料按照與DUT的實體接口協定寫入到接口上,對DUT形成有效激勵。
  • 當有必要是,driver在每解析并消化完一個sequence item後,它會将最後的狀态資訊寫會sequence item對象再傳回給sequencer,最終抵達sequence對象一側。
  • 這麼做目的在于,有時sequence需要得到driver于DUT互動的狀态,這就需要driver有一個回路将更新過的sequence item對象寫會至seqence一側。
  • sequence item是driver與DUT沒一次互動的最小顆粒度内容。
  • 例如DUT如果是一個slave端,driver扮演master去通路DUT的寄存器,那麼sequence item需要定義的資料資訊至少包括通路位址、指令碼、數值和狀态值,這樣的資訊在driver取得後,會通過時序方式在interface一側發起激勵發送至DUT。
  • 按照一般總線做寄存器通路的習慣,這種通路的時序上大緻會保持幾個時鐘周期,直至資料傳送完畢,而由driver再準備發起下一次操作。
  • 使用者除了可以在聲明sequence item時添加必要的成員變量,也可以添加對這些成員變量進行操作的成員方法。這些添加了的成員變量,需要沖鋒考慮在通過sequencer傳遞到driver前是否需要随機化。
  • 激勵驅動鍊的最後一道關卡是driver。
  • 對于常見的用法,driver往往是一個sequence item消化完,報告給sequencer和sequence,同時再請求消化下一個sequence item。
  • driver看起來永遠喂不飽,同時還有對事物很挑剔。在消化每一個sequence item之前,該item種的資料是已經随機化好的,是以每個item内容一般都是各不相同的。
  • driver自己并不會輕易修改item中的值,它會把item中的資料按照與DUT的實體協定時序關系驅動到接口上面。
  • 例如對于一個标準的寫操作,driver不但需要按照時序一次驅動位址總線,指令碼總線和資料總線,還應該等待從的傳回信号和狀态值,這樣才算完成一個資料寫傳輸。

1.3 繼承關系

  • uvm_sequence_item和uvm_sequence都是基于uvm_object,它們不同于uvm_component隻應當在build階段作為UVM環境的“不動産”進行建立和配置,而是可以在任何階段建立。
    晶片漫遊指南(4) -- UVM序列
  • 這種類的繼承帶來的UVM應用差別在于:
  • 由于無法判定環境在run階段什麼時間點會建立sequence和将其挂載(attach)到sequencer上面,是有意無法通過UVM環境結構或者phase機制來識别sequencer和item。
  • 考慮到uvm_sequence和uvm_sequence_item并不處于UVM結構當中,是以頂層在做配置時,無法按照層次關系直接配置到sequence中。
  • sequence一旦活動起來,它必須挂載到一個sequencer上,這樣sequence可以依賴于sequencer的結構關系,間接通過seeuencer來擷取頂層的配置和更多資訊。

1.4 提示

  • 從正常的認知方式來看,使用者可能更願意将時序控制的權利賦予sequence。這種從sequence産生item,繼而将item通過sequencer推送給driver的方式,實際上有一些“越俎代庖”的嫌疑。
  • 如果明确劃分責任的話,sequence應該隻負責生成item的内容,而不應該控制item消化的方式和時序,而驅動激勵時序的任務應當由driver來完成。
  • item的生成和傳送,并不表示最終的接口驅動時序,決定這一點的還包括sequencer和driver。sequencer之是以作為一個“路由”管道,設立在sequence和driver質安監,我們看重的是它的兩個特點:
    • sequencer作為一個元件,它可以通過TLM端口與driver傳送item對象。
    • sequencer在面向多個并行sequence時,它有充分的仲裁機制來合理配置設定和傳送item,繼而實作并行item資料傳送至driver的測試場景。

1.5 總結

  • 資料傳送機制采用的是get模式而不是put模式。我們在TLM傳輸中為渎職介紹過兩種典型的資料傳送場景,如果是put模式,那麼應該是sequencer将資料put至driver,而如果是get模式,那麼應當是driver從sequencer擷取item。
  • 之所有選擇get模式,UVM是基于下面的考慮:
    • 如果get模式,那麼當item從sequence産生,穿過sequencer到達driver時,我們就可以結束該傳輸(假如不需要傳回值的話)。而如果是put模式,則必須是sequencer将item傳送至driver,同時必須到傳回值才可以發起下一次的傳輸。這從效率上看,是有差别的。
    • 如果需要讓sequencer擁有仲裁特性,可以使得多個sequence同時挂載到sequencer上面,那麼get模式更符合“工學設計”。這是因為driver作為initiator,一旦發出get請求,它會先通過sequencer,繼而獲得仲裁後的item。

2 Sequence和item

2.1 概述

  • 無論是自駕item,穿過sequencer交通站通往終點driver,還是坐上sequencer大巴,一路沿途觀光,最終跟随導遊停靠到風景點driver。
  • 在接受如何駕駛item和sequence,準守什麼交規,最終可以有序穿過sequencer抵達之前,有必要首先認識sequence與item之間的關系。
  • 這裡的sequence指的是uvm_sequence類,而item指的是uvm_sequence_item類,我們簡稱其為sequence和item。
  • 對于激勵生成的場景控制,是由sequence來編制的,而對于激勵所需要的具體資料和控制要求,則是從item的成員資料得來的。
  • item是基于uvm_object類,這表明了它具備UVM核心基類所必要的資料操作方法,例如copy()、clone()、compare()、record()等。
  • item通常應該具備有什麼類型的資料成員呢?我們将它們劃分為如下基類:
    • 控制類。譬如總線協定上的讀寫類型、資料長度、傳送模式等。
    • 負載類。一般指資料總線上的資料包。
    • 配置類。用來控制driver的驅動行為,例如指令driver的發送間隔或者無錯誤插入。
    • 調試類。用來标記一些額外資訊友善調試,例如該對象的執行個體序号、建立時間,被driver解析的時間始末等。

2.2 Sequence和Item示例

class bus_trans extends uvm_sequence_item;
	rand bit write;
	rand int data;
	rand int addr;
	rand int delay;
	static int id_unm;
	`uvm_object_utils_begin(bus_trans)
		`uvm_field_int...
	`uvm_object_utils_end
	...
endclass
class test1 extends uvm_test;
	`uvm_component_utils(test1)
	...
	task run_phase(uvm_phase phase);
		bus_trans t1,t2;
		phase.raise t1,t2;
		phase.raise_objection(phase);
		#100ns
		t1 = new("t1");
		t1.print();
		#200ns;
		t2 = new("t2");
		void'(t2.randomize());
		t2.print();
		phase.drop_objection(phase);
	endtask
endclass
           
晶片漫遊指南(4) -- UVM序列
  • item使用時的特點:
    • 如果資料域屬于需要用來做驅動,那麼使用者應考慮定義為rand類型,同時按照驅動協定給出合适的constranint。
    • 由于item本身的資料屬性,為了充分利用UVM域聲明的特性,我們建議将必要的資料成員都通過`uvm_fielf_xxx宏來聲明,以便日後uvm_object的基本資料方法自動實作,例如上面的print()函數。
    • t1沒有被随機化而t2被随機化了,這種差别在item通往sequence之前是很明顯的。UVM要求item的建立和随機化都應該發生在sequence的body()任務中,而不是在sequencer或者driver中。
    • 按照item對象的聲明周期來區分,它的生命應該開始與sequence的body()方法,而後經理了随機化并穿越sequencer最終到達drivver,直到driver消化之後,它的生命一般來講才會結束。之所有要突出這一點,是因為一些使用者在使用中會不恰當地直接操作item對象,直接修改其中的資料,或者将他的句柄發送給其他元件使用,這回無形中修改item的資料基因,或者延長一個item對象的壽命。這種不合适的對象操作方式是需要注意的,可以取代的方式則是合理利用copy()和clone()等資料方法。

2.3 item與sequence的關系

  • 一個sequence可以包含一些有序組織起來的item執行個體,考慮到item在建立後需要被随機化,sequence在聲明時需要預留一些可供外部随機化的變量,這些随機變量一部分是通過層次傳遞限制來最終控制item對象的随機變量,一部分是用來item對象之間加以組織和時序控制的。
  • 為了區分幾種常見的sequence定義方式,我們介紹sequence之前首先将其分類為:
    • 扁平類(flat sequence)。這一類往往隻用來組織更細小的粒度,即item執行個體構成的組織。
    • 層次類(hierarchical sequence)。這一類是由更高層的sequence用來組織底層的sequence,進而讓這些sequence或者按照順序的方式,或者按照并行方式,挂載到同一個sequencer上。
    • 虛拟類(virtual sequence)。這一類則是最終控制整個測試場景的方式,鑒于整個環境中往往存在不同種類的sequencer和其他對應的sequence,我們需要一個虛拟的sequence來協調頂層的測試場景。之是以稱這個方式為virtual sequence,是因為該序列本身并不會固定挂載于某一個sequencer類型上,而是将其内部不同類型sequence最終挂載到不用的目标sequencer上面,這也是virtual sequence不同于hierarchical sequence的最大一點。

2.4 Flat Sequence介紹

  • 一個flat sequence往往由細小的sequence item群落構成,在此之上sequence還有更多的資訊來玩呗它需要實作的激勵場景。
  • 一般對于flat sequence而言,它包含的資訊有:
    • sequence item以及相關的constraint用來關聯生成的item之間的關系,進而完善出一個flat sequence的時序形态。
    • 除了限制sequence item的内容,各個item之間的時序資訊也需要由flat sequence給定,例如何時生成下一個item并且發送給driver。
    • 對于需要與driver握手的情況(例如讀操作),或者等待mointor事件進而做出反應(例如slave的memory response資料響應操作),都需要sequence在收到另外一側元件的狀态後,再決定下一步操作,即響應具體事件進而建立對應的item并且發送出去。

2.5 示例一

class flat_seq extends uvm_sequence;
	rand int length;
	rand int addr;
	rand int data[];
	rand bit write;
	rand int delay;
	constraint cstr{
		data.size() == length;
		foreach(data[i] soft data[i] == i;
		soft addr == 'h100;
		soft write == 1;
		delay inside {[1:5]};);
	`uvm_object_utils(flat_seq)
	...
	task body();
		bus_trans tmp;
		foreach(data[i])begin
			tmp = new();
			tmp.randomize() with {data == local::data[i];
								  addr == local::addr +i<<2;
								  write == local::write;
								  delay == local::delay;
};
			tmp.print();
		end
	endtask
calss bus_trans extends uvm_sequence_item;
	rand bit write;
	rand int data;
	rand int addr;
	rand int delay;
	static int id_num;
	`uvm_oject_utils_begin(bus_trans)
		`uvm_filed_int...
	`uvm_object_utils_end
	...
endclass
class test1 extends uvm_test;
	`uvm_component_utils(test1)
	...
	task run_phase(uvm_phase phase);
		flat_seq seq;
		phase.raise_objection(phase);
		seq = new();
		seq.randomize() with{addr == 'h200;length ==2;};
		seq.body();
		phase.drop_objection(phase);
	endtask
endclass
           
晶片漫遊指南(4) -- UVM序列
  • 我們展示沒有使用sequence的宏或者其他發送Item的宏來實作sequence/item與sequencer之間的傳送,而是用更直白的方式來描述這種層次關系,
  • flat_seq類可以看做事一個更長的資料包,資料包的具體内容、長度、位址等資訊都包含在flat_seq中。在生成Item過程中,通過将自身随機變量作為constraint内容來限定item随機變量,這是flat sequence的大緻處理方法。
  • 上面例碼沒有給出例如

    uvm_do/

    uvm_do_with/`uvm_create等宏是為了首先認清sequence與item之間的關系。是以該例也隻給出flat_seq::body()任務重建立和随機化item,而省略了發送item。
  • 實際上Bus_transk理應容納更多的時序内容,而不應該隻作為一次資料傳輸。作為資料傳送的最小顆粒,使用者們有權将它們擴充到更大的資料和時間範圍,進而間接減小資料通信的成本,提高整體運作效率。

2.6 示例2

class bus_trans extends uvm_sequence_item;
	rand bit write;
	rand int data[];
	rand int length;
	rand int addr;
	rand int delay;
	static int id_num;
	constraint cstr{
		data.size() == length;
		foreach(data[i]) soft data[i] ==i;
		soft addr == 'h100;
		soft write == 1;
		delay inside {[1:5]};}
	`uvm_object_utils_begin(bus_trans)
		`uvm_filed_...
	`uvm_object_utils_end
	...
endclass

class flat_seq extends uvm_sequence;
	rand int length;
	rand int addr;
	`uvm_object_utils(flat_seq)
	...
	task body();
		bus_trans tmp;
		tmp = new();
		tmp.randomize() with {length == local::length;addr == local::addr;};
		tmp.print();
	endtask
endclass

class test1 extends uvm_test;
	`uvm_component_utils(test)
	...
	task run_phase(uvm_phase phase);
		flat_seq seq;
		phase.raise_objection(phase);
		seq = new();
		seq.randomize() with {addr == 'h200;length == 3;};
		seq.body();
		phase.drop_objection(phase);
	endtask
endclass
           
晶片漫遊指南(4) -- UVM序列
  • 我們可以将一端完整發生在資料傳輸中的、更長的資料都“收編”在一個bus_trans類中,提高這個item粒度的抽象層次,讓它變得更有“氣質”。而一旦擁有了更成熟的、更合适切割的item,上層的flat sequence在使用過程中就會更順手一些了。
  • flat_seq類不再操本不屬于自己的閑心去考慮資料内容,而隻應該考慮這個資料包的長度、位址等資訊,因為擴充随機資料的責任一般由item負責就足夠了,使用flat_seq的使用者無需考慮多餘的資料限制。

2.7 Hierarchical Sequence介紹

  • Hierarchical sequence差別于flat sequence的地方在于,它可以使用其他的sequence,當然還有item,這麼做事為了建立更豐富的激勵場景。
  • 通過層次嵌套關系,可以讓Hierarchical sequence使用其他Hierarchical sequence、flat sequence和sequence item,這也意味着,如果底層的sequence item和flat sequence的粒度得當,那麼使用者就可以充分複用這些sequence/item來構成更加多樣的hierarchical sequence。
  • 接下來就定義的bus_trans和flat_seq給出一個簡單的hier_seq類,幫助了解這些sequence/item類之間的聯系。
class hire_seq extends uvm_sequence;
	`uvm_object_utils(hier_seq)
	function new(string name = "hire_seq");
		super.new(name);
	endfunction
	task body();
		bus_trans t1,t2;
		flat_seq s1,s2;
		`uvm_do_with{t1,{length == 2;}}
		fork
			`uvm_do_with(s1,{length == 5;})
			`uvm_do_with(s2,{length == 8;})
		join
		`uvm_do_with(t2,{length == 3;})
	endtask
endclass
           
  • 從hier_seq::body()來看,它包含有bus_trans t1,t2和flat_seq s1,s2,而它的層次關系就展現了對于各個sequence/item的協調上面。例碼中使用了`uvm_do_with宏,這個宏完成了三個步驟:
    • sequence或者item的建立
    • sequence或者item的随機化
    • sequence或者item的傳送
  • 差別于之間的例碼,這個例子通過`uvm_do_with宏幫助了解所謂的sequence腹痛就是通過高層的sequence/item,最後來建立期望的場景。
  • 在示例中既有傳銷的激勵關系,也有并行的激勵關系,而在更複雜的場景中,使用者可以考慮加入事件同步,或者一定的延遲關系來構成sequence/item之間的時序關系。

3 Sequencer和Driver

3.1 概述

晶片漫遊指南(4) -- UVM序列
  • driver同sequencer之間的TLM通信采取了get模式,即由driver發起請求,從sequencer一端獲得item,再由sequencer将其傳遞至driver。
  • 作為driver,它往往是一個“永動機”,胃口很大的家夥,永遠停不下來,隻要它可以從sequencer擷取item,它玖穿着紅舞鞋一直跳下去。
  • sequencer和item隻應該在合适的時間點産生需要的資料,而至于怎麼處理,則回由driver來實作。
  • 為了便于item傳輸,UVM專門定義了比對的TLM端口供sequencer和driver使用:
    • uvm_sequ_item_pull_port#(type REQ=int,type RSP=REQ)
    • uvm_seq_item_pull_export#(type REQ=int,type RSP=REQ)
    • uvm_seq_item_pull_imp#(type REQ=int,type RSP=REQ,type imp = int)
  • 由于driver是請求發起端,是以在driver一側例化了下面兩種端口:
    • uvm_seq_item_pull_port #(REP,RSP)seq_item_port
    • uvm_analysis_port#(RSP)rsp_port
  • 而sequencer一側則為請求的響應端,在sequencer一側例化了對應的兩種端口:
    • uvm_seq_item_pull_imp #(REQ,RSP,this_type)seq_item_export
    • uvm_analysis_export #(RSP)rsp_export
  • 通常情況下,使用者可以通過比對的第一對TLM端口完成item的完整傳送,即driver::seq_item_port.connect(sequencer::seq_item_export)完成。
  • 這一類端口功能主要用來實作driver與sequencer的request擷取和response傳回。
  • 這一種類型的TLM端口支援如下方法:
    • task get_next_item(output REQ req_arg):采取blocking的方式等待從sequence擷取下一個item。
    • task try_next_item(output REQ req_arg):采取nonblocking的方式從sequencer擷取item,如果立即傳回的結果req_arg為null,則表示sequence還沒有準備好。
    • function void item_done(input REQ req_arg=null):用來通知sequence目前的sequence item已經消化完畢,可以選擇性地傳遞RSP參數,傳回狀态值。
    • task wait_for_sequences():等待目前的sequence準備好而且可以擷取下一個有效的item,則傳回1,否則傳回0.
    • function void put_response(input RSP rsp_arg):采取nonblocking方式發送response,如果成功傳回1,否則傳回0。
    • task get(output REQ req_arg):采取get方式擷取item。
    • task peek(output REQ req_arg):采取peek方式擷取item。
    • task put(input RSP rsp_arg):采取blocking方式将response發送回sequence。
  • 讀者在這裡需要了解關于REQ和RSP類型的一緻性,由于uvm_sequencer與uvm_driver實際上都是參數化的類:
    • uvm_sequencer #(type REQ=uvm_sequence_item,RSP=REQ)
    • uvm_driver#(type REQ=uvm_sequence_item,RSP=REQ)
    • 使用者在自定義sequencer或者driver的時候,它們可以使用預設類型type REQ=uvm_sequence_item,以及RSP與REQ類型保持一緻。
  • 這有一個潛在的類型轉換要求,即driver得到REQ對象再進行下一步處理時,需要進行動态的類型轉換,将REQ轉換為uvm_sequence_item的子類型材可以從中擷取有效的成員資料。
  • 另外一種可行的方式是在自定義sequencer和driver時就表明了其傳遞的具體item類型,這樣就不用在進行額外的類型轉換了。
  • 通常情況下RSP類型與REQ類型保持一緻,這麼做的好處是為了便于統一處理,友善item對象拷貝、修改等操作。
  • driver消化完目前的request之後,可以通過item_done(input RSP rsp_arg=null)方法來告知sequence此次傳輸已經結束,參數中的RSP可以現在填入,傳回相應的狀态值。
  • driver也可以通過put_response()或者put()方法來單獨發送response。此外發送response還可以通過成對的uvm_driver::rsp_port和uvm_driver::rsp_export端口來完成,方法為uvm_driver::rsp_port::write(RSP)。

3.2事務傳輸執行個體

class bus_trans extends uvm_sequence_item;
	rand int data;
	`uvm_objetc_utils_begin(bus_trans)
		``uvm_field_int(data,UVM_ALL_ON)
	`uvm_object_utils_end
	...
endclass
class flat_seq extends uvm_sequence;
	`uvm_object_utils(flat_seq)
	...
	task body();
		uvm_sequence_item tmp;
		bus_trans req,rsp;
		tmp = create_item(bus_trans::get_type(),m_sequencer,"req");
		void'($cast(req,tmp));
		start_item(req);
		req.randmize with {data == 10;};
		`uvm_info("SEQ",$sformatf("sent a item \n %s",req.sprint());UVM_LOW)
		finish_item(req);
		get_response(tmp);
		void'($cast(rsp,tmp));
		`uvm_info("SEQ",$sformatf("got a item \n %s",rsp.sprlit()),UVM_LOW)
	endtask
endclass		
class sequencer extends uvm_sequencer;
	`uvm_component_utils(sequencer)
	...
endclass
class driver extends uvm_driver;
	`uvm_component_utils(driver)
	...
	task run_phase(uvm_phase phase);
		REQ tmp;
		bus_trans req,rsp;
		seq_item_port.get_next_item(temp);
		void'($cast(req,tmp));
		`uvm_info("DRV",$sformatf("got a item \n %s",req.sprint()),UVM_LOW)
		void'($cast(rsp,req.clone()));
		rsp.set_sequence_id(req.get_sequence_id());
		rsp.data += 100;
		seq_item_port.item_done(rsp);
		`uvm_info("DRV",$sformatf("sent a item \n %s",rsp.sprint()),UVM_LOW)
	endtask
endclass

class env extends uvm_env;
	sequencer sqr;
	driver drv;
	`uvm_component_utils(env)
	...
	function void build_phase(uvm_phase phase);
		sqr = sequencer::type_id::create("sqr",this);
		drv = driver::type_id::type_id::create("drv",this);
	endfunction
	function void connect_phase(uvm_phase phase);
		drv.seq_item_port.connect(sqr.seq_item_export);
	endfunction
endclass

class test1 extends uvm_test;
	env e;
	`uvm_component_utils(test1)
	...
	function void build_phase(uvm_phase phase);
		e = env::type_id::create("e",this);
	endfunction
	task run_phase(uvm_phase phase);
		flat_seq seq;
		phase.raise_objection(phase);
		seq = new();
		seq.start(e.sqr);
		phase.drop_onjection(phase);
	endtask
endclass
           
輸出結果:
UVM_INFO @ 0:uvm_test_top.e.sqr@@flat_seq [SEQ] sent a item
...
UVM_INFO @ 0:uvm_test_top.e.drv [DRV] got a item
...
UVM_INFO @ 0:uvm_test_top.e.drv [DRV] sent a item
...
UVM_INFO @ 0:uvm_test_top.e.sqr@@flat_seq [SEQ] got a item
...
           
  • 展示了從item定義,到sequence定義,最後到sequencer與driver的連接配接,即sequencer和driver之間的item傳輸過程,幫助了解傳輸的七點、各個節點以及終點。
  • 一旦了解了這中期的樸素原理,那麼這兩個元件之間的握手也就不再那麼神秘了。
  • 對于了解driver從sequencer擷取item,經過時序處理再傳回給sequence的握手過程很有幫助。
  • 在定義sequencer時,預設了REQ類型為uvm_sequence_item類型,這與稍後定義driver采取預設REQ類型保持一緻。
  • flat_seq作為動态建立的資料生成的載體,它的主任務flat_seq::body()做了如下幾件事情:
    • 通過方法create_item()建立request item對象。
    • 調用start_item()準備發送item。
    • 在完成發送item之前對item進行随機處理。
    • 調用finish_item()完成item發送。
    • 有必要的情況下可以從driver哪裡擷取response item。
  • 在定義driver時,它的任務driver::run_phase()也應通常做出如下處理:
    • 通過seq_item_port.get_next_item(REQ)從sequencer擷取有效的request item。
    • 從request item中擷取資料,進而産生資料激勵。
    • 對request item進行克隆生成新的對象response item。
    • 修改response item中的資料成員,最終通過seq_item_port.item_done(RSP)将response item對象傳回給sequence。
  • 對于uvm_sequence::get_response(RSP)和uvm_driver::item_done(RSP)這種成對的操作,是可選的而不是必須的,即使用者可以選擇uvm_driver不傳回response item,同時sequence也無需擷取response item。

3.3 通信時序

  • 無論是sequence還是driver,它們通話的對象都是sequencer,當多個sequence試圖要挂載到同一個sequencer上時,設計sequencer的仲裁功能。
  • 重點分析sequencer作為sequence與driver之間握手的橋梁,是如何扮演好這一角色的。
  • 我們将抽取去這三個類的主要方法,利用時間箭頭示範出完整的TLM通信過程。
    晶片漫遊指南(4) -- UVM序列
  • 對于sequence而言,無論是flat sequence還是hierarchical sequence,進一步切分的話,流向sequencer的都是sequece item,是以每個item的“成長周期”來看,它起始于create_item(),繼而通過start_item()嘗試從sequencer擷取可以通過的權限。
  • 對于seqencer的仲裁機制和使用方法我們暫且略過,而driver一側将一直處于“吃不飽” 的狀态,如果它沒有了item可以使用,将調用get_next_item()來嘗試從sequencer一側擷取item。
  • 在sequencer将通過權限交給某一個底層的sequence前,目标sequence中的item應該完成随機化,繼而在擷取sequence的通過權限後,執行finish_item()。
  • 在多個sequence同時向sequencer發送item時,就需要有ID資訊表明該item從那個sequence來,ID資訊在sequence建立item時就指派了。
  • 在到達driver之後,這個ID也可以用來跟蹤它的sequence資訊,使得運輸的使用更加安全,sequencer可以根據ID資訊來分發這些response item傳回值正确的sequence源頭。
  • 建議使用者driver中,通過clone()方式單獨建立response item,保證response item和response item兩個對象的獨立性。
  • 也許有的使用者為了“簡便”,在使用了resuest item之後,就直接修改它的資料并作為要傳回給sequence的response item之後,就直接修改它的資料作為傳回給sequence的response item,這麼做看來似乎節能環保,但實際上殊不知可能埋下隐患,一方面它鹽城了本來應該進垃圾桶的request item的壽命,同時也無法再對request item原始生成的資料做出有效記錄。
  • 為了統一起見,使用者可以在不定義sequence或者driver時指定sequence item類型,使用預設類型REQ = uvm_sequence_item,但是使用者需要主要在driver一側的類型轉換,例如對get_next_item(REQ)的傳回值REQ句柄做出動态類型轉換,待得到正确類型之後再進行接下來的操作。
  • 有的時候如果要複用一些驗證IP,使用者需要修改原有的底層sequece item。從處于驗證複用的角度,我們建議通過繼承原有的sequence item的方式定義新的Item子類,同時在頂層通過factory override的方式用心的item類型替換原有的item類型。

4. Sequencer和Sequence

4.1概述

  • 了解了sequence與driver之間傳遞sequence item的握手過程,同時也掌握了sequence與item之間的關系。
  • 接下來需要就sequence挂載到sequencer的常用方法做出總結,大家抗原通過對這些常用方法的宏的介紹,了解到它們不同的使用場景。
  • 面對多個sequence如果需要同時挂載到sequence時,那就面臨着仲裁的需要,uvm_sequencer自帶有仲裁特性,結合sequence的優先級設定,最終可以實作想要的效果。

sequence宏概述

  • 對于UVM初學者,我們往往給出的建議是,如果可以正确差別方法start()和宏`uvm_do,那就拿下了sequence發送和嵌套的半壁江山。
  • 然而考慮到讀者對技藝的高要求,我們這裡會系統性地闡述各種方法和宏之間的關系,以及讨論什麼時候可以使用方法,什麼時候可以使用宏。
  • 對于已經習慣于sequence宏使用的使用者而言,當它們再切回到sequence方法、或者調試這些方法時,會有一種不适感,但是如果你想要對sequence發送做出更準确的控制,我們還須正本清源,首先熟悉sequence的方法。

4.2 sequence和item發送執行個體

class bus_trans extends uvm_sequence_item;
	rand int data;
	`uvm_object_utils_begin(bus_trans)
		`uvm_field_int(data,UVM_ALL_ON)
	`uvm_objection_utils_end
	...
endclass

class child_seq extends uvm_sequence;
	`uvm_object_utils(child_seq)
	...
	task body();
		uvm_sequence_item tmp;
		bus_trans req;
		tmp = create_item(bus_trans::get_type(),m_sequencer,"req");
		void'($cast(req,tmp));
		start_item(req);
		req.randomize with {data == 10;};
		finsih_item(req);
	endtask
endclass
class top_seq extends uvm_sequence;
	`uvm_object_utils(top_seq)
	...
	task body();
		uvm_sequence_item tmp;
		child_seq cseq;
		bus_trans req;
		//create child sequence anda items
		cseq = child_seq::type_id::create("cseq");
		tmp = create_item(bus_trans::get_type(),m_sequencer,"req");
		//send child sequence via start()
		cseq.start(m_sequence,this);
		//send sequence item
		void'($cast(req,tmp));
		start_item(req);
		req.randomize with {data==20;};
		finish_item(req);
	endtask
endclass	

class sequence extends uvm_sequencer;
	`uvm)component_utils(sequencer)
	...
endclass

class driver extends uvm_driver;
	`uvm_component_utils(driver)
	...
	task run_phase(uvm_phase phase);
		REQ tmp;
		bus_trans req;
		forever begin
			seq_item_port.get_next_item(tmp);
			void'($cast(req,tmp));
			`uvm_info("DRV",$sformatf("got a item \n %s",req.split()),UVM_LOW)
			seq_item_port.item_done();
		end
	endtask
endclass
class enc enxtends uvm_enc;
	sequencer sqr;
	driver drv;
	`uvm_component_utils(env)
	...
	function void build_phase(uvm_phase phase);
		sqr = sequencer::type_id::create("sqr",this);
		drv = driver::type_id::create("drv",this);
	endfunction
	function void connect_phase(uvm_phase phase);
		drv.seq_item_port.connect(sqr.seq_item_export);
	endfuction
endclass
           
輸出結果為:
UVM_INFO @ 0:uvm_test_top.e.drv [DRV] got a item
...
UVM_INFO @ 0:uvm_test_top.e.drv [DRV] got a item
...
           
  • 在這段例碼中,主要使用了兩種方法,第一個方法時針對将sequence挂載到sequencer上的應用。
  • uvm_sequence::start(uvm_sequence_base sequencer,

    uvm_sequence_base parnet_sequence = null

    int this_priority = -1,bit call_pre_post = 1)

  • 在使用該方法的過程中,使用者首先應該指明sequencer的句柄,如果該sequence是頂部sequence,即沒有更上層的sequence嵌套它,則它可以省略對第二個參數parent_sequence的指定。
  • 第三個參數的預設值-1會使得該sequence如果有parent_sequence會繼承其優先級值,如果他是頂部(root)sequence,則其優先級會被自動設定為100,使用者也可以自己指定優先級數值
  • 第四個參數建議使用預設值,這樣的話uvm_sequence::pre_body()和uvm_sequence::post_body()兩個方法會在uvm_sequence::body()的前後執行。
  • 在上面的例子中,child_seq被嵌套到top_seq中,繼而在挂載是需要指定parent_sequence;而在test一層調用top_seq時,由于它是root sequence,則不需要再指定parent sequence,這一點使用者需要主要。另外,在調用挂載sequence時,需要對這些sequence進行例化。
  • 第二種發送方法時針對将Item挂載到sequencer上的應用。

    uvm_sequence::start_item(uvm_sequence_item item,int set_priority = -1,

    uvm_sequencer_base sequencer = null);

    uvm_sequence::finish_item(uvm_sequence_item item,int set_priority = -1);

  • 對于start_item()的使用,第三個參數使用者需要注意的是否需要将item挂載到"非目前parent sequence挂載的sequencer"上面,有點繞口是嗎?簡單來說,如果你想将item和其parent sequence挂載到不同的sequencer上面,你就需要指定這個參數。
  • 在使用這一方法時,使用者除了需要記得建立item,例如通過uvm_object::create()或者uvm_sequence::create_item(),還需要在它們之間完成item的随機化處理。
  • 從這一點建議來看,需要讀者了解到,對于一個item的完整傳送sequence要在sequencer一側獲得通過權限,才可以順利将item發送至driver。我們可以通過拆解這些步驟得到更多的細節:
    • 建立item。
    • 通過start_item()方法等待獲得sequencer的授權許可,其後執行parent sequence的方法pre_do()。
    • 對item進行随機化處理。
    • 通過finish_item()方法在對item進行了随機化處理之後,執行parent sequence的mid_do(),以及調用uvm_sequencer::send_request()和uvm_sequencer::wait_for_item_done()來講item發送至sequencer再完成與driver之間的握手。最後,執行了parent_sequence的post_do()。
  • 這些完成的細節有兩個部分需要注意。
    • 第一,sequence和item自身的優先級,可以決定什麼時候可以獲得sequencer的授權。
    • 第二,讀者需要意識到,parent sequence的虛方法pre_do()、mid_do()和post_do()會發生在發送item的過程中間。
  • 如果對比start()方法和start_item()/finish_item(),讀者首先要厘清它們面向的挂載對象是不同的。
  • 在執行start()過程中,預設情況下會執行sequence的pre_body()和post_body(),但是如果start()的參數call_pre_post = 0,那麼就不會這樣執行,是以在一些場景中,UVM使用者會奇怪為什麼pre_body()和post_body()沒有被執行。
  • pre_body()和post_body()并不是一定會被執行的,這一點同UVM的phase順序執行是有差別的。
  • 下面一段代碼是對start()方法執行過程的自然代碼描述(資料引用),讀者可以看到它們執行的順序關系和條件:
    晶片漫遊指南(4) -- UVM序列
  • 對于pre_do()、mid_do()、post_do()而言,子一級的sequence/item在被發現過程中會間接調用parent sequence的pre_do()等方法。
  • 隻要在參數傳遞過程中,確定子一級sequence/item與parent sequence的聯系,那麼這些執行過程是會按照上面的描述依次執行的。
  • 下面我們也給出一段start_item()/finish_item()的自然代碼描述,來表示執行發送item時的相關方法執行順序:
    晶片漫遊指南(4) -- UVM序列

4.3 發送序列的相關宏

晶片漫遊指南(4) -- UVM序列
- 正是通過幾個sequence/item宏來打天下的方式,使用者可以通過`uvm_do/ `uvm_do_with來發送無論是sequence還是item。這種不區分對象時sequence還是item的方式,帶來了不少便捷,但也容易引起verifier們的惰性。是以在使用它們之前,需要先了解它們背後的sequence和item各自發送的方法。
- 不同的宏,可能會包含建立對象的過程也可能不會建立對象。例如`uvm_do/`uvm_do_with會建立對象,而`uvm_send則不會建立對象,也不會将對象随機處理,是以要了解它們各自包含的執行内容和順序。
- 此外還有其它的宏,例如,将優先級作為參數傳遞的`uvm_do_pri/`uvm_do_on_prio等,還有專門針對sequence的`uvm_create_seq/`uvm_do_seq/`uvm_do_seq_with等宏。
           

4.4 sequencer的仲裁特性介紹

  • uvm_sequencer類自建了仲裁機制來保證多個sequence在勇士挂載到sequencer時,可以按照仲裁規則運作特定sequence中的item優先通過。
  • 在實際使用中,我們可以通過uvm_sequencer::set_arbitrantion(UVM_SEQ_ARB_TYPE val)函數來設定仲裁模式,這裡的仲裁模式UVM_SEQ_ARB_TYPE有下面幾種值可以選擇:
    • UVM_SEQ_ARB_FIFO:預設模式。來自于sequences的發送請求,按照FIFO先進先出的方式被依次授權,和優先級沒有關系。
    • UVM_SEQ_ARB_WEIGHTED:不同的sequence的發送請求,将按照它們的優先級權重随機授權。
    • UVM_SEQ_ARB_RANDOM:不同的請求會被随機授權,而無視它們的抵達順序和優先級。
    • UVM_SEQ_ARB_STRICT_FIFO:不同的請求,會按照它們的最高優先級随機授權,與抵達時間無關。
    • UVM_SEQ_ARB_USER:使用者可以自定義仲裁方法user_priority_arbitration()來裁定那個sequence請求被随機授權。
      晶片漫遊指南(4) -- UVM序列
  • 在上面的仲裁模式中,與priority有關的模式有UVM_SEQ_ARB_WEIGHTED、UVM_SEQ_ARB_STRICT_FIFO和UVM_SEQ_ARB_STRICT_RANDOM。
  • 這三種模式差別在于,UVM_SEQ_ARB_WEIGHTED的授權可能會落到各個優先級sequence的請求上面,而UVM_SEQ_ARB_STRICT_RANDOM則隻會将授權随機安排待最高優先級的請求上面,UVM_SEQ_ARB_STRICT_FIFO則不會随機授權,而是嚴格按照優先級以及抵達順序來依次授權。
  • 沒有特别的要求,使用者不需要再額外自定義授權機制,依次使用UVM_SEQ_ARB_USER這一模式的情況不多見,其他模式可以滿足絕大多數的仲裁請求。
  • 鑒于sequence傳送的優先級可以影響sequencer的仲裁授權,我們有必要結合sequencer的仲裁模式和sequence的優先級給出一段例碼。

4.5 Sequencer的仲裁示例

class bus_trans extends uvm_sequence_item;
	rand int data;
	...
endclass

class child_seq extends uvm_sequence;
	rand int base;
	...
	task body();
		bus_trans req;
		repeat(2) `uvm_do_with(req,{data inside {[base:base+9]};})
	endtask
endclass
class top_seq extends uvm_sequence;
	...
	task body();
		child_seq seq1,seq2,seq3;
		m_sequencer_set_arbitration(UVM_SEQ_ARB_STRICT_FIFO);
		fork
			`uvm_do_pri_with(seq1,500,(base == 10;))
			`uvm_do_pri_with(seq2,500,{base == 20;})
			`uvm_do_pri_with(seq3,300,{base == 30;})
		join
	endtask
endclass

class sequencer extends uvm_sequencer;
	...
endclass
class driver extends uvm_driver;
	...
	task run_phase(uvm_phase phase);
		REQ tmp;
		bus_trans req;
		forever begin
			seq_item_port.get_next_item(tmp);
			void'($cast(req,tmp));
			`uvm_info("DRV",$sformatf("got a item %0d from parent sequence %s",req.data,req.get_parent_sequence().get_name()),UVM_LOW)
			seq_item_port.item_done();
		end
	endtask
endclass
class env extends uvm_env;
	sequencer sqr;
	driver drv;
	...
	function void build_phase(uvm_phase phase);
		sqr = sequencer::type_id::create("sqr",this);
		drv = driver::type_id::create("drv",this);
	endfunction
	function void connect_phase(uvm_phase phase);
		drv.seq_item_port.connect(sqr.seq_item_export);
	endfunction
endclass
class test1 extends uvm_test;
	env e;
	...
	task run_phase(uvm_phase phase);
		top_seq seq;
		phase.raise_objection(phase);
		seq = new();
		seq.start(e.sqr);
		phase.drop_objection(phase);
	endtask
endclass	
           
輸出結果為:
UVM_INFO @ 0:umv_test_top.e.drv [DRV] got a item 16 from parent sequence seq1
UVM_INFO @ 0:uvm_test_top.e.drv [DRV] got a item 22 from parent sequence seq2
UVM_INFO @ 0:uvm_test_top.e.drv [DRV] got a item 19 from parent sequence seq1
UVM_INFO @ 0:uvm_test_top.e.drv [DRV] got a item 23 from parent sequence seq2
UVM_INFO @ 0:uvm_test_top.e.drv [DRV] got a item 33 from parent sequence seq3
UVM_INFO @ 0:uvm_test_top.e.drv [DRV] got a item 32 from parent sequence seq3
           
  • 上面的例碼中,seq1、seq2、seq3在同一時刻發起傳送請求,通過`uvm_do_pri_with的宏,在發送sequence時可以發傳遞優先級參數。
  • 由于将seq1和seq2設定為同樣的高優先級,而seq3設定為較低的優先級,這樣在随後的UVM_SEQ_ARB_STRICT_FIFO仲裁模式下,可以從輸出結果看到,按照優先級高低和傳送請求的順序,先将seq1和seq1中的item發送完畢,随後将seq3發送完。
  • 除了sequence遵循仲裁機制,在一些特殊情況下,有一些sequence需要有更高權限取得sequencer的授權來通路driver。例如在需要響應中斷的情況下,用于進行中斷的sequence應該有更高的權限來擷取sequencer的授權。

4.6 uvm_sequencer鎖定機制

  • uvm_sequencer提供了兩種鎖定機制,分别通過lock()和grab()方法實作,這兩種方法的區分在于:
    • lock()與unlock()這一對方法可以為sequence提供排外的通路權限,但前提條件是,該sequence首先需要按照sequencer的仲裁機制獲得授權。而一旦sequence獲得授權,則無需擔心權限被收回,隻有該sequence主動解鎖(unlock)它的sequencer,才可以釋放這一鎖定的權限。lock()是一種阻塞任務,隻有獲得來了權限,它才會傳回。
    • grab()與ungrab()也可以為sequence提供排外的通路權限,而且它隻需要在sequencer下一次授權周期時就可以無條件獲得授權。與lock方法相比,grab方法無視同一時刻内發起傳送請求的其它sequence,而唯一可以阻止它的隻有已經預先獲得授權的其他lock或者grab的sequence。
    • 這裡需要注意的是,由于“解鈴還須系鈴人”,如果sequence使用了lock()或者grab()方法,必須在sequence結束前調用unlock()或者ungrab()方法來釋放權限,否則sequencer會進入死鎖狀态而無法繼續為其餘sequence授權。

      sequencer鎖定示例

class bus_trans extends uvm_sequence_item;
	...
endclass
class child_seq extends uvm_sequence;
	...
endclass
class lock_seq extends uvm_sequence;
	...
	task body();
		bus_trans req;
		#10ns;
		m_sequencer.lock(this);
		`uvm_info("LOCK","get exclusive access by lock()",UVM_LOW)
		repeat(3) #10ns `uvm_do_with(req,{data inside{[100:110]};})
		m_sequencer.unlock(this);
	endtask
endclass
class grab_seq extends uvm_sequence;
	...
	task body();
		bus_trans req;
		#20ns;
		m_sequencer.grab(this);
		`uvm_info("GRAB","get exclusive access by grab()",UVM_LOW)
		repeat(3) #10ns `uvm_do_with(req,{data inside{[200:210]};})
		m_sequencer.ungrab(this);
	endtask
endclass
class top_seq extends uvm_sequence;
	...
	task body();
		child_seq seq1, seq2, seq3;
		lock_seq locks;
		grob_seq grabs;
		m_sequencer.set_arbitration(UVM_SEQ_ARB_STRICT_FIFO);
		fork
			`uvm_do_pri_with(seq1,500,{base == 10;})
			`uvm_do_pri_with(seq2,500,{base == 20;})
			`uvm_do_pri_with(seq3,300,{base == 30;})
			`uvm_do_pri(locks,300)
			`uvm_do(grabs)
		join
	endtask
endclass
           
晶片漫遊指南(4) -- UVM序列
  • 集合例碼和輸出結果,我們從中可以發現如下幾點:
    • 對于sequence locks,在10ns時它跟其他幾個sequence一同向sequencer發起請求,按照仲裁模式,sequencer先後授權給seq1、seq2、seq3,最後才授權給locks。
    • 而locks在獲得授權之後。就可以一直享有權限而無需擔心權限被sequencer收回,locks結束前,使用者需要通過unlock()方法返還權限。
    • 對于sequence grabs,盡管他在20ns發起了請求權限(實際上seq1、seq2、seq3也在同一時刻發起了請求權限),而由于權限已經被locks占用,是以它無權收回權限。
    • 是以隻有當locks在40ns結束時,grabs才可以在sequencer沒有被鎖定的狀态下擷取權限,而grabs在條件下擷取權限是無視同一時刻發起請求的其它sequence的。
    • 同樣的,在grabs結束前,也應當通過ungrab()方法釋放權限,防止sequencer的死鎖行為。

4.7 sequence的階層化

概述

  • 伴随着對sequence/item發送方式的了解,讀者也需要從之前4位初出茅廬的verifier梅、尤、婁、董他們的角度來看,如何完成驗證的水準複用和垂直複用。
  • 就水準複用而言,在MCDF各個子子產品的驗證語境中,它值得是如何利用已有的資源,完成高效的激勵場景建立。
  • 就垂直複用來看,它指的是在MCDF子系統驗證中,可以完成結構複用和激勵場景複用兩個方面。這節的垂直複用主要關注于激勵場景複用。
  • 無論是水準複用還是垂直複用,激勵場景的複用很大程度上取決于如何設計sequence,使得底層的sequence實作合理的粒度,幫助完成水準複用,進一步依托于底層激勵場景,最終可以實作底層到高層的垂直複用。
  • 本節就MCDF的實際驗證場景出發,引申出以下概念來完善sequence的階層化:
    • hierarchical sequence
    • virtual sequence
    • layering sequence
  • 通過對這三個與sequence階層化有關的概念解讀和實際場景分析,我們希望讀者本節後可以就不同sequence場景複用設計出适合自己驗證場景的sequence結構,三者在特定情況下可有機組合,為整體的驗證複用提供良好的支援。