文章目錄
1.寄存器模型( Register model )簡介
- UVM的寄存器模型是一組高級抽象的類,用來對DUT中具有地址映射的寄存器和存儲器進行建模。它非常貼切的反映DUT中寄存器的各種特性,可以產生激勵作用於DUT並進行寄存器功能檢查。通過UVM的寄存器模型,可以簡單高效的實現對DUT的寄存器進行前門或後門操作。它本身也提供了一些寄存器測試的sequence,方便用戶直接使用。
- UVM的寄存器模型是高度抽象化的,不依賴具體DUT而獨立存在的。它使用一箇中間變量
uvm_reg_bus_op
描述register的訪問信息,用戶必須創建一個繼承自uvm_reg_adapter
的類,實現uvm_reg_bus_op與真正作用到具體dut上的transaction的互相轉換。 - RAL: Register Abstraction Layer
- UVM寄存器模型基本結構如下圖所示。
uvm_reg_block
是UVM register layer的類,其層次化的結構反映了DUT的Register狀況。uvm_reg_adapter
是必不可少的,它實現了bus driver需要的transaction和中間變量uvm_reg_bus_op
直間的相互轉換。寄存器前門訪問是依靠寄存器模型自動產生sequence,併發送給bus driver來完成的。
2.UVM 寄存器模型的層次結構
- uvm_reg_field是寄存器模型的最小單位,和DUT的每個register裏的bit filed對應。
- uvm_reg 和dut中每個register對應,其寬度一般和總線位寬一致,裏面可以包含多個uvm_reg_field。
- uvm_reg_block裏包含uvm_reg,一般一個最底層模塊級的DUT的所有寄存器,具有相同的基地址,會放在一個reg_block中。uvm_reg_block內也可包含其他低層次的reg_block。
reg_block裏有含有uvm_reg_map
類的對象default_map,進行地址映射,以及用來完成寄存器前後門訪問操作。
一個寄存器模型必須包含一個reg_block。
一個reg_block可以包含多個reg_map, 從而實現一個reg_block應用到不同總線,或不同地址段上。 - uvm_mem是對dut中memory進行建模使用的。
- 這些類均是繼承自uvm_object類。一個完整的register model, 均有這些層次化的register元素構成,放到頂層的reg block中。一個reg block可以包含子block,register,register file和memories,如下圖所示。
- 需要說明的是,因爲一個項目中存在大量的寄存器,用人工來維護RAL不僅耗時耗力,更容易出現錯誤,所以正常情況下應該使用工具產生和維護UVM寄存器模型 , 下面介紹幾個工具:
- Synopsy VCS中自帶的ralgen工具可以產生uvm 寄存器模型,具體使用方法可參考UVM Register Abstraction Layer Generator User Guide(uvm_ralgen_ug.pdf)。
- 當然Candance和Mentor均有自己的工具。
- Paradigm Works公司開源的 RegWorks Spec2Reg 。
- 此外還有agnisys公司的**IDesignSpec**,其最早版本也曾供大家免費使用。
3. 創建和使用寄存器模型
Step1: 對每個寄存器進行定義
class cfs_dut_reg_ctrl extends uvm_reg;
rand uvm_reg_field reserved; //reserved
rand uvm_reg_field enable; //control for enabling the DUT
`uvm_object_utils(cfs_dut_reg_config)
function new(string name = "cfs_dut_reg_config");
//specify the name of the register, its width in bits and if it has coverage
super.new(name, 32, 1);
endfunction
virtual function void build();
reserved = uvm_reg_field::type_id::create("reserved");
//specify parent, width, lsb position, rights, volatility,
//reset value, has reset, is_rand, individually_accessible
reserved.configure(this, 31, 1, "RO", 0, 0, 1, 1, 1);
enable = uvm_reg_field::type_id::create("enable");
enable.configure(this, 1, 0, "RW", 0, 0, 1, 1, 1);
endfunction
endclass
-
在uvm_reg的new中,要將寄存器的寬度傳入super.new()的第二個參數,super.new()的第三個參數是
uvm_coverage_model_e
類型,用以設置寄存器是否參與加入覆蓋率:Value Description UVM_NO_COVERAGE None UVM_CVR_REG_BITS Individual register bits UVM_CVR_ADDR_MAP Individual register and memory addresses UVM_CVR_FIELD_VALS Field values UVM_CVR_ALL All coverage models -
uvm_reg類有一個build函數,這個build和UVM_component的bulid_phase並不一樣,並不會自動執行,需要手動調用。
-
要使用uvm_field的configure函數對各個field進行詳細配置,它有9各參數:
enable.configure( .parent ( this ), .size ( 3 ), .lsb_pos ( 0 ), .access ( "RW" ), .volatile ( 0 ), .reset ( 0 ), .has_reset ( 1 ), .is_rand ( 1 ), .individually_accessible( 0 ) ); 參數一是此域的父輩,也就是此域位於哪個寄存器中,即是this; 參數二是此域的寬度; 參數三是此域的最低位在整個寄存器的位置,從0開始計數; 參數四表示此字段的存取方式; 參數五表示是否是易失的(volatile),這個參數一般不會使用; 參數六表示此域上電覆位後的默認值; 參數七表示此域時都有復位; 參數八表示這個域是否可以隨機化; 參數九表示這個域是否可以單獨存取。
Step2: 將寄存器放入register block容器中,並加入到對應的Address Map
class cfs_dut_reg_block extends uvm_reg_block;
`uvm_object_utils(cfs_dut_reg_block)
//Control register
rand cfs_dut_reg_ctrl ctrl;
//Status register
rand cfs_dut_reg_status status;
function new(string name = "cfs_dut_reg_block");
super.new(name, UVM_CVR_ALL);
ctrl = cfs_dut_reg_ctrl::type_id::create("ctrl");
status = cfs_dut_reg_status::type_id::create("status");
endfunction
virtual function void build();
default_map = create_map(“default_map”,0,4,UVM_BIG_ENDINA,0);
ctrl.configure(this, null, "");
ctrl.build();
default_map.add_reg(ctrl,`h10,"RW")
status.configure(this, null, "");
status.build();
default_map.add_reg(status,`h14,"RO")
endfunction
endclass
- reg block中也有build函數,在其中要做如下事情:
- 調用create_map函數完成default_map的實例化,
default_map = create_map(“default_map”,0,2,UVM_BIG_ENDINA,0);
create_map的第一個參數是名字,第二個參數是該reg block的基地址,第三個參數是寄存器所映射到的總線的寬度(單位是byte,不是bit),第四個參數是大小端,第五個參數表示該寄存器能否按byte尋址。 - 完成每個寄存器的build及configure操作
uvm_reg的configure函數原型:function void configure ( uvm_reg_block blk_parent, uvm_reg_file regfile_parent = null, string hdl_path = "" )
其第一個參數是所在reg block的指針,第二個參數是reg_file指針,第三個是寄存器後面訪問路徑—string類型。 - 把每個寄存器加入到default_map中。uvm_reg_map存有各個寄存器的地址信息。
第一個參數是要添加的寄存器名,第二個是地址,第三個是寄存器的讀寫屬性。default_map.add_reg(ctrl, `h10,"RW")
- 調用create_map函數完成default_map的實例化,
- 如果一個寄存器可以通過兩個物理總線訪問,則需要將其添加到多個address map中。
Step3: 創建Register Adapter
寄存器模型的前門操作都會通過sequence產生一個uvm_reg_bus_op
類型的變量,他不能直接被bus sequencer和driver接受。需要定義一個繼承自uvm_reg_adpater
的adapter,來完成與bus transaction之間的轉換,之後才能交給交給bus_sequencer和bus_driver,實現前門訪問。 而從bus_driver返回的rsp,也需要由adapter轉換成uvm_reg_bus_op類型變量,返回給寄存器模型,用來更新內部值。
在adapter中,要實現:
1. reg2bus: 其作用是將uvm_reg_bus_op類型變量轉換成bus_sequencer能夠接受的transaction。
virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
2. bus2reg: 其將收集到的transaction轉換成寄存器模型使用的uvm_reg_bus_op類型變量,用以更新寄存器模型中相應寄存器的值。
virtual function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
class cfs_dut_reg_adapter extends uvm_reg_adapter;
`uvm_object_utils(cfs_dut_reg_adapter)
function new(string name = "cfs_dut_reg_adapter");
super.new(name);
endfunction
virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
acme_apb_drv_transfer transfer = acme_apb_drv_transfer::type_id::create("transfer");
if(rw.kind == UVM_WRITE) begin
transfer.direction = APB_WRITE;
end
else begin
transfer.direction = APB_READ;
end
transfer.data = rw.data;
transfer.address = rw.addr;
return transfer;
endfunction
virtual function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
acme_apb_mon_transfer transfer;
if($cast(transfer, bus_item)) begin
if(transfer.direction == APB_WRITE) begin
rw.kind = UVM_WRITE;
end
else begin
rw.kind = UVM_READ;
end
rw.addr = transfer.address;
rw.data = transfer.data;
rw.status = UVM_IS_OK;
end
else begin
`uvm_fatal(get_name(), $sformatf("Could not cast to acme_apb_mon_transfer: %s",
bus_item.get_type_name()))
end
endfunction
endclass
Step4: 頂層reg block對象的創建及使用
整個仿真平臺只創建一個reg model對象,在其他地方使用指針調用。一般在test中創建頂層reg_block,及adapt和predictor.
在
// in base test class
...
irtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
//create all the elements of the environment
reg_block = cfs_dut_reg_block::type_id::create("reg_block",this);
apb_agent = acme_apb_agent::type_id::create("apb_agent", this);
adapter = cfs_dut_reg_adapter::type_id::create("adapter");
predictor = uvm_reg_predictor#(acme_apb_mon_transfer)::type_id::create("predictor", this);
reg_block.configure(null,"");
reg_block.build();
reg_block.lock_model();
reg_block.reset("HARD");
endfunction
...
reg_block傳完要調用其configure函數,配置後面訪問路徑。
Step5: 將Address Map連接到Bus sequencer和Adapter
在test或env的connect phase中,調用default_map或其他用戶自定義的address map對象中的set_sequencer方法, 並把前門操作的bus sequencer及adaptor作爲參數傳入。
virtual function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
//required to start physical register accesses using the registers
reg_block.default_map.set_sequencer(apb_agent.sequencer, adapter);
predictor.map = reg_block.default_map;
predictor.adapter = adapter;
apb_agent.monitor.output_port.connect(predictor.bus_in);
endfunction
Step6: 在sequence或其他component中使用寄存器模型
- 在sequence中使用:
要先在對應的sequencer中定義一個頂層reg_block的指針,並指向base_test的reg_block對應,之後再sequence中調用p_sequencer訪問,如:
p_sequencer.p_reg_block.enable.wirte(status,1,UVM_FRONTDOOR);
// 前門訪問 front-door
p_sequencer.p_reg_block.enable.re'a'd(status,value,UVM_BACKDOOR);
// 後門訪問 back-door
4 寄存器訪問方法
前門訪問和後面訪問的區別
- 前門訪問是通過物理總線向dut發起寄存器訪問操作,消耗仿真時間
- 後面操作不通過物理總線,不消耗仿真時間
前門訪問過程
以write爲例:
- 當調用寄存器的write()任務後,產生uvm_reg_item類型的transaction:rw,之後調用uvm_reg::do_write()。
- 在uvm_reg_map中,調用reg_adapter.reg2bus將rw轉換成bus driver對應的transaction。
- 把transaction交給sequencer,最終由bus driver驅動到對應的bus interface上。
- bus monitor在bus interface上檢測到bus transaction 。
- reg_predictor會調用reg_adapter.bus2reg將該bus transaction轉換成uvm_reg_item。
- 從driver中返回的req會轉換成uvm_reg_item類型,如防止sequencer的response隊列溢出,需要在adapter中設置provides_reponses.
- 寄存器模型根據返回的uvm_reg_item來更新寄存器的value,m_mirrored和m_desired三個值