UVM basic template

Seungyun Lee·2026년 4월 10일

UVM

목록 보기
8/12

Test template

class base_test extends uvm_test;
    `uvm_component_utils(base_test)
    my_env env;
    
    function void build_phase(uvm_phase phase);
        // Create the environment using the factory
        env = my_env::type_id::create("env", this);
        // Pass standard configurations to the agents
        uvm_config_db#(int)::set(this, "env.agt*", "is_active", UVM_ACTIVE);
    endfunction
    
    // Catch-all timeout for safety
    function void start_of_simulation_phase(uvm_phase phase);
        uvm_top.set_timeout(1ms);
    endfunction
endclass

Agent template

class ahb_agent extends uvm_agent;
    `uvm_component_utils(ahb_agent)
    ahb_driver    drv;
    ahb_sequencer sqr;
    ahb_monitor   mon;
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        // 1. Monitor is ALWAYS built
        mon = ahb_monitor::type_id::create("mon", this);
        // 2. Logic to build Driver/Sequencer
        if(get_is_active() == UVM_ACTIVE) begin
            drv = ahb_driver::type_id::create("drv", this);
            sqr = ahb_sequencer::type_id::create("sqr", this);
        end
    endfunction
    
    function void connect_phase(uvm_phase phase);
    // Only connect if the agent is driving
    if(get_is_active() == UVM_ACTIVE) begin
        drv.seq_item_port.connect(sqr.seq_item_export);
    end
	endfunction

endclass

seq_item_port.get_next_item(req)


task run_phase(uvm_phase phase);
    forever begin
        // 1. Wait for physical request (e.g., Slave Select or Valid)
        @(posedge vif.req_valid);
        
        // 2. NOW ask the Sequencer: "The DUT is asking, what do I reply?"
        seq_item_port.get_next_item(req);
        
        // 3. Drive the response data from the sequence item
        vif.resp_data <= req.data;
        vif.resp_ready <= 1;
        @(posedge vif.clk);
        vif.resp_ready <= 0;
        
        // 4. Return control to sequence
        seq_item_port.item_done();
    end
endtask
                            

Driver

How the Driver Gets Transactions

seq_item_port.get_next_item(req):
This is a blocking call. The driver sleeps here until a sequence provides a new transaction.

seq_item_port.item_done():
This is a non-blocking signal back to the sequencer. It unblocks the finish_item() call in the sequence.

VIF

Drivers do not access DUT signals directly. They use a Virtual Interface (VIF)

class axi_driver extends uvm_driver #(axi_item);
    `uvm_component_utils(axi_driver)
    virtual axi_if vif;
    task run_phase(uvm_phase phase);
        // Reset the interface
        vif.valid <= 0;
        forever begin
            // Get the transaction
            seq_item_port.get_next_item(req);
            // DRIVE logic
            @(posedge vif.clk);
            vif.addr  <= req.addr;
            vif.data  <= req.data;
            vif.valid <= 1;
            // Wait for DUT acknowledgment
            wait(vif.ready == 1);
            @(posedge vif.clk);
            vif.valid <= 0;
            // Complete handshake
            seq_item_port.item_done();
        end
    endtask
endclass

Pipelined Handshaking

Modern protocols (like AXI or PCIe) are pipelined. This means a driver might start a new request before the previous response has finished.

The Pipelining Pattern:
Instead of a simple loop, you use fork...join_none to handle the "Data Phase" and "Address Phase" in parallel.

task run_phase(uvm_phase phase);
    forever begin
        seq_item_port.get_next_item(req);
        // 1. Driving Phase (Blocking)
        drive_address_phase(req); 
        // 2. Data Phase (Non-blocking / Background)
        fork
            automatic axi_item req_copy = req;
            drive_data_phase(req_copy);
        join_none
        // 3. Immediately ask for next item while data drives
        seq_item_port.item_done();
    end

Monitor

Sampling via Virtual Interfaces


class spi_monitor extends uvm_monitor;
    `uvm_component_utils(spi_monitor)
    virtual spi_if vif;
    uvm_analysis_port #(spi_item) mon_ap;
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        mon_ap = new("mon_ap", this);
    endfunction
    task run_phase(uvm_phase phase);
        forever begin
            spi_item item = spi_item::type_id::create("item");
           
           // 1. Wait for start of transaction
            @(negedge vif.cs_n);
           
           // 2. Sample data bits over 8 clock cycles
            for (int i = 0; i < 8; i++) begin
                @(posedge vif.sclk);
                item.data[7-i] = vif.mosi;
            end
            // 3. COMPLETE: Reconstructed the object!
            // 4. BROADCAST: Send to anyone listening
            mon_ap.write(item);
        end
    endtask
endclass
                            

Analysis Port Mechanics

The mon_ap.write(item)
call is the ONLY way a monitor should communicate with the outside world. It implement a Publisher-Subscriber pattern:

Scoreboard

profile
Design Verification engineer

0개의 댓글