前回はこちら:msyksphinz.hatenablog.com
UVMのさらなるテストベンチを試行するために、以下のウェブサイトのサンプルを試してみることにした。
今回は以下のスイッチ。アドレスに対してどちらかの出力に入力を転送する。この時に1サイクルのDelayが入る。
switch.sv
module switch #( parameter ADDR_WIDTH = 8, parameter DATA_WIDTH = 16, parameter ADDR_DIV = 8'h3f ) ( input logic clk, input logic rstn, input logic vld, input logic [ADDR_WIDTH-1: 0] addr, input logic [DATA_WIDTH-1: 0] data, output logic [ADDR_WIDTH-1: 0] addr_a, output logic [DATA_WIDTH-1: 0] data_a, output logic [ADDR_WIDTH-1: 0] addr_b, output logic [DATA_WIDTH-1: 0] data_b );
このDUTに対してテストベンチを接続するためのインタフェース switch_if
を以下のように定義する。
switch_if.sv
interface switch_if (input bit clk); logic rstn; logic vld; logic [ 7: 0] addr; logic [15: 0] data; logic [ 7: 0] addr_a; logic [15: 0] data_a; logic [ 7: 0] addr_b; logic [15: 0] data_b; endinterface // switch_if
sequence item
DUTの入出力を接続するためのシーケンス・アイテム。
スコアボードへの容易な接続を可能にする。
rand bit addr, data
はランダム生成されることが前提となる。
switch_item.sv
class switch_item extends uvm_sequence_item; rand bit [ 7: 0] addr; rand bit [15: 0] data; bit [ 7: 0] addr_a; bit [15: 0] data_a; bit [ 7: 0] addr_b; bit [15: 0] data_b; // Use utility macros to implement standard functions // like print, copy, clone, etc `uvm_object_utils_begin(switch_item) `uvm_field_int (addr, UVM_DEFAULT) `uvm_field_int (data, UVM_DEFAULT) `uvm_field_int (addr_a, UVM_DEFAULT) `uvm_field_int (data_a, UVM_DEFAULT) `uvm_field_int (addr_b, UVM_DEFAULT) `uvm_field_int (data_b, UVM_DEFAULT) `uvm_object_utils_end function new (string name = "switch_item"); super.new(name); endfunction // new endclass // switch_item
Driver
DUTをドライブするためのインタフェース。
build_phase()
によってDUTのswitch_vif
のインタフェースを受け取る。
run_phase()
ではswitch_item
を生成して新しいシーケンス・アイテムを受け取り、これをdrive_item
で駆動する。
drive_item()
ではvif
を駆動してDUTを動作させる。
driver.sv
class driver extends uvm_driver #(switch_item); `uvm_component_utils(driver) function new(string name = "driver", uvm_component parent=null); super.new(name, parent); endfunction // new virtual switch_if vif; virtual function void build_phase(uvm_phase phase); super.build_phase(phase); if (!uvm_config_db #(virtual switch_if)::get (this, "", "switch_vif", vif)) begin `uvm_fatal ("DRV", "Could not get vif") end endfunction // build_phase virtual task run_phase (uvm_phase phase); super.run_phase (phase); forever begin switch_item m_item; `uvm_info ("DRV", $sformatf ("Wait for item from sequencer"), UVM_LOW) seq_item_port.get_next_item(m_item); drive_item(m_item); seq_item_port.item_done(); end endtask // run_phase virtual task drive_item (switch_item m_item); vif.vld <= 1'b1; vif.addr <= m_item.addr; vif.data <= m_item.data; @ (posedge vif.clk); vif.vld <= 1'b0; endtask // drive_time endclass // driver
Monitor
Virtual Interfaceをモニタリングするためのモニタ。
DUTのピンの駆動をモニタリングして、switch_item
に格納し、analysis port
に転送する。
build_phase()
では、DUTのswitch_vif
を取得する。
run_phase()
では、2つのタスクを動かしている。sample_port("Thread0")
とsample_port("Thread1")
である。
sample_port()
では、vif
が駆動した記録をswitch_item
に格納し、mon_analysis_port
に書き込む。
これは2段階に分かれており、最初は入力のaddr/data
を読み込み、次のサイクルにaddr_a/data_a/addr_b/data_b
を格納する。
この2サイクルの処理をThread0とThread1が順番に行うために、セマフォsema4
を使ってsample_port()
を交互に動かしていく。
monitor.sv
class monitor extends uvm_monitor; `uvm_component_utils(monitor) function new (string name="monitor", uvm_component parent=null); super.new(name, parent); endfunction // now uvm_analysis_port #(switch_item) mon_analysis_port; virtual switch_if vif; semaphore sema4; virtual function void build_phase (uvm_phase phase); super.build_phase(phase); if (!uvm_config_db #(virtual switch_if)::get(this, "", "switch_vif", vif)) begin `uvm_fatal("MON", "Could not get vif") end sema4 = new(1); mon_analysis_port = new("mon_analysis_port", this); endfunction // build_phase virtual task run_phase (uvm_phase phase); super.run_phase(phase); fork sample_port("Thread0"); sample_port("Thread1"); join endtask // run_phase virtual task sample_port (string tag=""); // This task monitors the intereface for a complete // transaction and pushes into the mailbox when the // transaction is complete forever begin @(posedge vif.clk); if (vif.rstn & vif.vld) begin switch_item item = new; sema4.get(); item.addr = vif.addr; item.data = vif.data; `uvm_info("MON", $sformatf("T=%0t [Monitor] %s First part over", $time, tag), UVM_LOW); @(posedge vif.clk); sema4.put(); item.addr_a = vif.addr_a; item.data_a = vif.data_a; item.addr_b = vif.addr_b; item.data_b = vif.data_b; mon_analysis_port.write(item); `uvm_info("MON", $sformatf("T=%0t [Monitor] %s Second part over, item:", $time, tag), UVM_LOW); item.print(); end // if (vif.rstn & vif.vld) end // forever begin endtask // sample_port endclass // monitor
Agent
Agentはドライバ・モニタ・シーケンサをまとめるためのクラス。
d0
(Driver)のs0
(Sequencer)との接続を行う。
agent.sv
class agent extends uvm_agent; `uvm_component_utils(agent) function new(string name="agent", uvm_component parent=null); super.new(name, parent); endfunction // new driver d0; // Driver handle monitor m0; // Monitor handle uvm_sequencer #(switch_item) s0; // Sequencer Handle virtual function void build_phase (uvm_phase phase); super.build_phase (phase); s0 = uvm_sequencer #(switch_item)::type_id::create("s0", this); d0 = driver::type_id::create("d0", this); m0 = monitor::type_id::create("m0", this); endfunction // build_phase virtual function void connect_phase (uvm_phase phase); super.connect_phase(phase); d0.seq_item_port.connect(s0.seq_item_export); endfunction // connect_phase endclass // agent
Scoreboard
uvm_analysis_imp
を経由してスコアボードを更新する。
スコアボードに対して書き込みが行われると、switch_item item
を検査し、入力とその出力が想定通りなのかをチェックする。
scoreboard.sv
class scoreboard extends uvm_scoreboard; `uvm_component_utils (scoreboard) function new(string name="scoreboard", uvm_component parent=null); super.new(name, parent); endfunction // new uvm_analysis_imp #(switch_item, scoreboard) m_analysis_imp; virtual function void build_phase(uvm_phase phase); super.build_phase(phase); m_analysis_imp = new("m_analysis_imp", this); endfunction // build_phase virtual function write(switch_item item); if (item.addr inside {[0:'h3f]}) begin if (item.addr_a != item.addr | item.data_a != item.data) begin `uvm_error("SCBD", $sformatf ("ERROR! Mismatch addr=0x%0h data=0x%0h addr_a=0x%0h data_a=0x%0h", item.addr, item.data, item.addr_a, item.data_a)); end else begin `uvm_info("SCBD", $sformatf ("PASS! Match addr=0x%0h data=0x%0h addr_a=0x%0h data_a=0x%0h", item.addr, item.data, item.addr_a, item.data_a), UVM_LOW); end end else begin if (item.addr_b != item.addr | item.data_b != item.data) begin `uvm_error("SCBD", $sformatf ("ERROR! Mismatch addr=0x%0h data=0x%0h addr_b=0x%0h data_b=0x%0h", item.addr, item.data, item.addr_b, item.data_b)); end else begin `uvm_info("SCBD", $sformatf ("PASS! Match addr=0x%0h data=0x%0h addr_a=0x%0h data_a=0x%0h", item.addr, item.data, item.addr_b, item.data_b), UVM_LOW); end end // else: !if(item.addr inside {[0:'h3f]}) endfunction // write endclass // scoreboard