Part 1 · Foundations · Intermediate

Layered Testbench Architecture: Test, Env, Agent, DUT

The standard UVM layering pattern — where protocol VIP, scoreboards, virtual sequencers, and the DUT interface belong, and why layering is really about isolating change.

The four-layer model

Almost every UVM testbench, from a tiny block to a full SoC, is organized into four conceptual layers. Each layer has one job and hides the layer below it.

  1. Test — selects the scenario, applies factory overrides, sets plusarg-driven modes, and starts the top sequence. Thin and disposable.

  2. Environment — composes agents, scoreboards, predictors, coverage collectors, and the register model. Reusable infrastructure with minimal scenario logic.

  3. Agent — encapsulates a driver + sequencer + monitor for exactly one interface instance, plus its configuration.

  4. Interface / DUT — pin-level connectivity exposed to the class world through a virtual interface from the static top module.

diagram
+-------------------- uvm_test --------------------+
         |  picks scenario, overrides, starts top sequence  |
         +--------------------------+-----------------------+
                                    |
         +-------------------- uvm_env ---------------------+
         |  agents + scoreboard + coverage + reg_model      |
         +------+------------------+-----------------+------+
                |                  |                 |
            uvm_agent          scoreboard       reg_model (RAL)
          +----+-----+
          |    |     |
       driver sqr  monitor
          |          |
       +--+----------+--+
       |  virtual iface |
       +-------+--------+
               |
            +--+--+
            | DUT |
            +-----+

The agent: the reusable unit of protocol knowledge

The agent is the heart of reuse. It bundles everything needed to talk to one interface and exposes an is_active knob so the same class can either drive traffic or just observe.

systemverilog
class apb_agent extends uvm_agent;
  `uvm_component_utils(apb_agent)

  apb_driver    drv;
  apb_sequencer sqr;
  apb_monitor   mon;
  apb_config    cfg;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if (!uvm_config_db#(apb_config)::get(this, "", "cfg", cfg))
      `uvm_fatal("NOCFG", "apb_config not set")

    mon = apb_monitor::type_id::create("mon", this);   // always built
    if (cfg.is_active == UVM_ACTIVE) begin             // only when active
      drv = apb_driver   ::type_id::create("drv", this);
      sqr = apb_sequencer::type_id::create("sqr", this);
    end
  endfunction
endclass
  • ACTIVE agent: builds driver + sequencer + monitor and drives the bus.

  • PASSIVE agent: builds only the monitor — used to observe an interface already driven elsewhere.

  • The same agent class is reused in block-level (active) and SoC-level (passive) testbenches.


Env and the virtual sequencer

The environment owns the reusable infrastructure and, when there are multiple agents, a virtual sequencer that holds handles to each agent's sequencer. Virtual sequences then coordinate cross-interface scenarios without any agent knowing about the others.

systemverilog
class soc_env extends uvm_env;
  `uvm_component_utils(soc_env)
  apb_agent      apb_agt;
  axi_agent      axi_agt;
  soc_scoreboard scb;
  soc_vsequencer v_sqr;

  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    apb_agt.mon.ap.connect(scb.apb_imp);
    axi_agt.mon.ap.connect(scb.axi_imp);
    v_sqr.apb_sqr = apb_agt.sqr;   // virtual sequencer maps real sequencers
    v_sqr.axi_sqr = axi_agt.sqr;
  endfunction
endclass

Why this layering pays off

  • Tests are thin orchestrators; the env holds everything reusable.

  • Multiple agents in one env = a multi-interface block or subsystem.

  • The virtual sequencer coordinates across agents without breaking agent encapsulation.

Key takeaways

  • Layering exists for change isolation: swap tests without rewriting agents, reuse agents across envs.

  • Keep protocol knowledge inside agents and integration knowledge inside the env.

  • is_active lets one agent serve both active driving and passive monitoring roles.

Common pitfalls

  • A monolithic env that hard-codes one test's sequence flow instead of staying generic.

  • A scoreboard reaching into driver internals instead of subscribing to the monitor's analysis port.

  • Putting scenario logic in the env, so every new test forces an env edit.