Structural Patterns: Adapter, Facade, and Composite in Verification

Structural patterns solve the puzzle of how classes and objects fit together. In verification, this matters deeply: we integrate third-party VIP, build hierarchical environments, and create unified interfaces over complexity. This post covers three patterns that every DV engineer uses—often without knowing their names.

The Problem: Integration Pain

You've been there. The project requires a new USB VIP from vendor X. Their interface looks nothing like your existing agent structure. Or you need to coordinate sequences across five different interfaces, but exposing all those sequencers creates chaos. Or your environment hierarchy has become a tangled mess of direct references.

The Gang of Four called these structural problems:

"Structural patterns are concerned with how classes and objects are composed to form larger structures... Structural class patterns use inheritance to compose interfaces or implementations."

Design Patterns, Gamma et al.

Three patterns dominate verification structural design: Adapter for integration, Facade for simplification, and Composite for hierarchy.

Adapter Pattern: Making Incompatible Interfaces Work

The Adapter pattern converts one interface into another that clients expect. It's the electrical adapter of software—same functionality, different plug shape.

classDiagram
    class Client {
        +operation()
    }
    class Target {
        <<interface>>
        +request()
    }
    class Adapter {
        -adaptee: Adaptee
        +request()
    }
    class Adaptee {
        +specificRequest()
    }
    
    Client --> Target
    Target <|.. Adapter
    Adapter --> Adaptee : delegates

DV Reality: Third-Party VIP Integration

Your testbench expects transactions through TLM analysis ports with a specific format. The vendor VIP uses a completely different transaction type and callback mechanism:

// Vendor VIP: Uses callbacks with their transaction type
class vendor_usb_monitor extends vendor_base_monitor;
  // Vendor's callback mechanism
  virtual function void packet_received_cb(vendor_usb_packet pkt);
    // Override this to get packets
  endfunction
endclass

// Your testbench expects:
class usb_transaction extends uvm_sequence_item;
  usb_pid_e    pid;
  bit [6:0]    addr;
  bit [3:0]    endpoint;
  bit [7:0]    data[$];
  usb_speed_e  speed;
  // ... your fields
endclass

Direct integration would mean modifying either the VIP or your entire testbench. Neither is acceptable.

Adapter Solution

// Adapter: Wraps vendor VIP, exposes your interface
class usb_vip_adapter extends uvm_component;
  `uvm_component_utils(usb_vip_adapter)
  
  // Your testbench's expected interface
  uvm_analysis_port #(usb_transaction) ap;
  
  // The adapted vendor component
  vendor_usb_monitor vendor_mon;
  
  function new(string name, uvm_component parent);
    super.new(name, parent);
    ap = new("ap", this);
  endfunction
  
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    vendor_mon = vendor_usb_monitor::type_id::create("vendor_mon", this);
    
    // Register our callback with vendor monitor
    vendor_mon.register_callback(this);
  endfunction
  
  // Callback from vendor VIP - adapt and forward
  virtual function void vendor_packet_cb(vendor_usb_packet vpkt);
    usb_transaction txn = convert_to_internal(vpkt);
    ap.write(txn);  // Publish in your format
  endfunction
  
  // The actual adaptation logic
  protected function usb_transaction convert_to_internal(vendor_usb_packet vpkt);
    usb_transaction txn = usb_transaction::type_id::create("txn");
    
    // Map vendor fields to your fields
    case (vpkt.packet_type)
      VENDOR_TOKEN_OUT:  txn.pid = USB_PID_OUT;
      VENDOR_TOKEN_IN:   txn.pid = USB_PID_IN;
      VENDOR_TOKEN_SETUP: txn.pid = USB_PID_SETUP;
      VENDOR_DATA0:      txn.pid = USB_PID_DATA0;
      VENDOR_DATA1:      txn.pid = USB_PID_DATA1;
      VENDOR_ACK:        txn.pid = USB_PID_ACK;
      VENDOR_NAK:        txn.pid = USB_PID_NAK;
      default:           txn.pid = USB_PID_RESERVED;
    endcase
    
    txn.addr = vpkt.device_address;
    txn.endpoint = vpkt.ep_num;
    txn.data = vpkt.payload;  // Assuming compatible queue type
    
    // Handle speed translation
    txn.speed = (vpkt.is_high_speed) ? USB_HIGH_SPEED :
                (vpkt.is_full_speed) ? USB_FULL_SPEED : USB_LOW_SPEED;
    
    return txn;
  endfunction
  
endclass

Now your scoreboard, coverage collectors, and checkers work unchanged—they receive usb_transaction through the analysis port, unaware of the vendor VIP underneath.

Object Adapter vs Class Adapter

The example above is an Object Adapter—it holds an instance of the adaptee. SystemVerilog also supports Class Adapter using multiple inheritance:

// Class Adapter: Inherits from both
// (Only works if vendor allows extension)
class usb_vip_adapter extends vendor_usb_monitor 
                      implements your_usb_interface;
  
  uvm_analysis_port #(usb_transaction) ap;
  
  // Override vendor callback
  virtual function void packet_received_cb(vendor_usb_packet pkt);
    usb_transaction txn = convert_to_internal(pkt);
    ap.write(txn);
  endfunction
  
endclass

Object Adapter is generally preferred—it's more flexible and doesn't require the adaptee to support inheritance.

Two-Way Adapter

Sometimes you need to adapt in both directions—send transactions to the VIP driver and receive from the monitor:

class usb_bidirectional_adapter extends uvm_component;
  `uvm_component_utils(usb_bidirectional_adapter)
  
  // Analysis port for received packets (your format)
  uvm_analysis_port #(usb_transaction) rx_ap;
  
  // Sequencer for sending packets (your format)
  uvm_sequencer #(usb_transaction) seqr;
  
  // Internal: vendor components
  protected vendor_usb_agent vendor_agent;
  
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    rx_ap = new("rx_ap", this);
    seqr = usb_transaction_sequencer::type_id::create("seqr", this);
    vendor_agent = vendor_usb_agent::type_id::create("vendor_agent", this);
  endfunction
  
  task run_phase(uvm_phase phase);
    // Launch adapter sequence that pulls from your sequencer,
    // converts, and drives to vendor driver
    usb_adapter_sequence adapter_seq = usb_adapter_sequence::type_id::create("adapter_seq");
    adapter_seq.upstream_seqr = seqr;  // Your sequencer
    adapter_seq.start(vendor_agent.sequencer);  // Vendor sequencer
  endtask
  
endclass

// Adapter sequence: translates on the fly
class usb_adapter_sequence extends uvm_sequence #(vendor_usb_packet);
  
  uvm_sequencer #(usb_transaction) upstream_seqr;
  
  task body();
    usb_transaction your_txn;
    vendor_usb_packet vendor_pkt;
    
    forever begin
      // Get transaction from your sequencer
      upstream_seqr.get_next_item(your_txn);
      
      // Convert to vendor format
      vendor_pkt = convert_to_vendor(your_txn);
      
      // Drive through vendor
      start_item(vendor_pkt);
      finish_item(vendor_pkt);
      
      upstream_seqr.item_done();
    end
  endtask
  
endclass

Facade Pattern: Simplifying Complex Subsystems

The Facade pattern provides a unified interface to a set of interfaces in a subsystem. It defines a higher-level interface that makes the subsystem easier to use.

classDiagram
    class Facade {
        +operationA()
        +operationB()
    }
    class SubsystemA {
        +methodA1()
        +methodA2()
    }
    class SubsystemB {
        +methodB1()
        +methodB2()
    }
    class SubsystemC {
        +methodC1()
    }
    class Client
    
    Client --> Facade
    Facade --> SubsystemA
    Facade --> SubsystemB
    Facade --> SubsystemC

DV Reality: Virtual Sequencer as Facade

A SoC testbench might have ten different agents. Without a facade, test sequences become coordination nightmares:

// BAD: Test knows too much about environment internals
class soc_test extends uvm_test;
  
  task run_phase(uvm_phase phase);
    // Test directly manipulates every sequencer
    fork
      begin
        cpu_seq.start(env.cpu_agent.sequencer);
      end
      begin
        dma_seq.start(env.dma_agent.sequencer);
      end
      begin
        pcie_seq.start(env.pcie_subsys.ep_agent.sequencer);
      end
      begin
        usb_seq.start(env.usb_host_agent.sequencer);
      end
      begin
        eth_seq.start(env.ethernet_agent.tx_sequencer);
      end
    join
  endtask
  
endclass

Problems: tests are tightly coupled to environment structure, coordination is manual, and restructuring the environment breaks every test.

Facade Solution: Virtual Sequencer

// Virtual sequencer: Facade over all agent sequencers
class soc_virtual_sequencer extends uvm_sequencer;
  `uvm_component_utils(soc_virtual_sequencer)
  
  // Handles to sub-sequencers (set during connect_phase)
  cpu_sequencer       cpu_seqr;
  dma_sequencer       dma_seqr;
  pcie_sequencer      pcie_seqr;
  usb_sequencer       usb_seqr;
  ethernet_sequencer  eth_seqr;
  gpio_sequencer      gpio_seqr;
  i2c_sequencer       i2c_seqr;
  spi_sequencer       spi_seqr;
  
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  
endclass

// Environment connects the facade
class soc_env extends uvm_env;
  
  soc_virtual_sequencer v_seqr;
  
  // All the agents
  cpu_agent     cpu_agt;
  dma_agent     dma_agt;
  pcie_agent    pcie_agt;
  usb_agent     usb_agt;
  ethernet_agent eth_agt;
  // ... more agents
  
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    
    // Wire up the facade
    v_seqr.cpu_seqr  = cpu_agt.sequencer;
    v_seqr.dma_seqr  = dma_agt.sequencer;
    v_seqr.pcie_seqr = pcie_agt.sequencer;
    v_seqr.usb_seqr  = usb_agt.sequencer;
    v_seqr.eth_seqr  = eth_agt.tx_sequencer;
  endfunction
  
endclass

Virtual Sequences Use the Facade

// Virtual sequence: clean, coordinated, decoupled
class dma_to_pcie_transfer_vseq extends uvm_sequence;
  `uvm_object_utils(dma_to_pcie_transfer_vseq)
  `uvm_declare_p_sequencer(soc_virtual_sequencer)
  
  task body();
    cpu_config_seq    cpu_cfg;
    dma_transfer_seq  dma_xfer;
    pcie_response_seq pcie_rsp;
    
    // Phase 1: CPU configures DMA
    `uvm_do_on(cpu_cfg, p_sequencer.cpu_seqr)
    
    // Phase 2: DMA transfer with PCIe completion
    fork
      `uvm_do_on_with(dma_xfer, p_sequencer.dma_seqr, {
        src_addr == 64'h1000;
        dst_addr == 64'h8000_0000;  // PCIe BAR
        length == 256;
      })
      `uvm_do_on(pcie_rsp, p_sequencer.pcie_seqr)
    join
  endtask
  
endclass

// Test is now simple
class dma_pcie_test extends base_test;
  
  task run_phase(uvm_phase phase);
    dma_to_pcie_transfer_vseq vseq;
    phase.raise_objection(this);
    
    vseq = dma_to_pcie_transfer_vseq::type_id::create("vseq");
    vseq.start(env.v_seqr);  // Just use the facade
    
    phase.drop_objection(this);
  endtask
  
endclass

Enhanced Facade: Coordination Services

A sophisticated facade can provide coordination services beyond simple access:

class soc_virtual_sequencer extends uvm_sequencer;
  `uvm_component_utils(soc_virtual_sequencer)
  
  // Sub-sequencers
  cpu_sequencer  cpu_seqr;
  dma_sequencer  dma_seqr;
  pcie_sequencer pcie_seqr;
  
  // Coordination services
  uvm_event_pool events;          // Shared events
  uvm_barrier    sync_barrier;    // Multi-agent sync
  semaphore      resource_lock;   // Shared resource access
  
  // Status tracking
  bit dma_busy;
  bit pcie_link_up;
  
  function new(string name, uvm_component parent);
    super.new(name, parent);
    events = new("events");
    sync_barrier = new("sync_barrier", 3);  // 3 participants
    resource_lock = new(1);
  endfunction
  
  // Facade methods for common operations
  task wait_for_pcie_link();
    if (!pcie_link_up) begin
      uvm_event link_evt = events.get("pcie_link_up");
      link_evt.wait_trigger();
    end
  endtask
  
  task acquire_dma_channel();
    resource_lock.get();
  endtask
  
  task release_dma_channel();
    resource_lock.put();
  endtask
  
  task sync_all_agents();
    sync_barrier.wait_for();
  endtask
  
endclass

// Sequences use facade services
class coordinated_vseq extends uvm_sequence;
  `uvm_declare_p_sequencer(soc_virtual_sequencer)
  
  task body();
    // Wait for subsystem ready using facade
    p_sequencer.wait_for_pcie_link();
    
    // Acquire shared resource
    p_sequencer.acquire_dma_channel();
    
    fork
      run_dma_sequence();
      run_pcie_response();
    join
    
    p_sequencer.release_dma_channel();
    
    // Synchronize before next phase
    p_sequencer.sync_all_agents();
  endtask
  
endclass

Composite Pattern: Part-Whole Hierarchies

The Composite pattern composes objects into tree structures representing part-whole hierarchies. Clients treat individual objects and compositions uniformly.

classDiagram
    class Component {
        <<abstract>>
        +operation()
        +add(Component)*
        +remove(Component)*
    }
    class Leaf {
        +operation()
    }
    class Composite {
        -children: Component[]
        +operation()
        +add(Component)
        +remove(Component)
    }
    
    Component <|-- Leaf
    Component <|-- Composite
    Composite o-- Component : children

DV Reality: UVM Environment Hierarchy

UVM's component hierarchy is a textbook Composite implementation:

  • Component: uvm_component (abstract base)
  • Leaf: uvm_driver, uvm_monitor, uvm_sequencer
  • Composite: uvm_agent, uvm_env, uvm_test
// UVM already implements Composite
class uvm_component;
  protected uvm_component m_children[string];  // Child storage
  
  // Composite operations
  virtual function void add_child(uvm_component child);
  virtual function uvm_component get_child(string name);
  virtual function void get_children(ref uvm_component children[$]);
  
  // Operation applied uniformly to tree
  virtual function void build_phase(uvm_phase phase);
  // ... more phases
endclass

When you call build_phase on a test, it recursively builds the entire tree. Composite in action.

Building Hierarchical Environments

The real design challenge is structuring your composites effectively:

// Leaf: Individual agent (simplest component)
class pcie_agent extends uvm_agent;
  `uvm_component_utils(pcie_agent)
  
  pcie_driver    drv;
  pcie_monitor   mon;
  pcie_sequencer seqr;
  
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if (get_is_active() == UVM_ACTIVE) begin
      drv = pcie_driver::type_id::create("drv", this);
      seqr = pcie_sequencer::type_id::create("seqr", this);
    end
    mon = pcie_monitor::type_id::create("mon", this);
  endfunction
  
endclass

// Composite: Subsystem groups related agents
class pcie_subsystem_env extends uvm_env;
  `uvm_component_utils(pcie_subsystem_env)
  
  // Child agents
  pcie_agent          root_port_agent;
  pcie_agent          endpoint_agent;
  pcie_config_agent   config_agent;
  
  // Child composites (nested!)
  pcie_phy_env        phy_env;
  
  // Subsystem-level components
  pcie_scoreboard     scoreboard;
  pcie_coverage       coverage;
  
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    
    root_port_agent = pcie_agent::type_id::create("rp_agent", this);
    endpoint_agent = pcie_agent::type_id::create("ep_agent", this);
    config_agent = pcie_config_agent::type_id::create("cfg_agent", this);
    phy_env = pcie_phy_env::type_id::create("phy_env", this);
    
    scoreboard = pcie_scoreboard::type_id::create("scoreboard", this);
    coverage = pcie_coverage::type_id::create("coverage", this);
  endfunction
  
endclass

// Higher-level composite: SoC environment
class soc_env extends uvm_env;
  `uvm_component_utils(soc_env)
  
  // Subsystem composites
  pcie_subsystem_env  pcie_subsys;
  usb_subsystem_env   usb_subsys;
  ethernet_env        eth_env;
  
  // Standalone agents
  cpu_agent           cpu_agt;
  memory_agent        mem_agt;
  
  // Top-level components
  soc_scoreboard      soc_sb;
  soc_virtual_sequencer v_seqr;
  
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    
    // Build composites (they build their children)
    pcie_subsys = pcie_subsystem_env::type_id::create("pcie_subsys", this);
    usb_subsys = usb_subsystem_env::type_id::create("usb_subsys", this);
    eth_env = ethernet_env::type_id::create("eth_env", this);
    
    // Build leaves
    cpu_agt = cpu_agent::type_id::create("cpu_agt", this);
    mem_agt = memory_agent::type_id::create("mem_agt", this);
    
    // Build top-level
    soc_sb = soc_scoreboard::type_id::create("soc_sb", this);
    v_seqr = soc_virtual_sequencer::type_id::create("v_seqr", this);
  endfunction
  
endclass

Custom Composite Operations

The power of Composite is defining operations that propagate through the tree:

// Interface for components that can be reset
interface class resettable;
  pure virtual task reset();
  pure virtual function bit is_idle();
endclass

// Leaf implements directly
class pcie_agent extends uvm_agent implements resettable;
  
  virtual task reset();
    if (drv != null) drv.reset_driver();
    seqr.stop_sequences();
    mon.reset_monitor();
  endtask
  
  virtual function bit is_idle();
    return (drv == null || drv.is_idle()) && mon.is_idle();
  endfunction
  
endclass

// Composite propagates to children
class pcie_subsystem_env extends uvm_env implements resettable;
  
  virtual task reset();
    // Reset all children in parallel
    fork
      root_port_agent.reset();
      endpoint_agent.reset();
      config_agent.reset();
      phy_env.reset();
    join
    
    // Reset own state
    scoreboard.flush();
  endtask
  
  virtual function bit is_idle();
    return root_port_agent.is_idle() &&
           endpoint_agent.is_idle() &&
           config_agent.is_idle() &&
           phy_env.is_idle();
  endfunction
  
endclass

// Top level can reset entire hierarchy uniformly
class soc_env extends uvm_env implements resettable;
  
  virtual task reset();
    fork
      pcie_subsys.reset();  // Propagates down
      usb_subsys.reset();
      eth_env.reset();
      cpu_agt.reset();
      mem_agt.reset();
    join
  endtask
  
endclass

// Usage: one call resets entire hierarchy
task run_phase(uvm_phase phase);
  // ... run test ...
  
  // Single call resets everything
  env.reset();
  
  // ... run more test ...
endtask

Composite for Hierarchical Checking

// Hierarchical checker composite
class checker_composite extends uvm_component;
  `uvm_component_utils(checker_composite)
  
  protected uvm_component checkers[$];
  protected int unsigned error_count;
  
  function void add_checker(uvm_component chk);
    checkers.push_back(chk);
  endfunction
  
  // Composite operation: aggregate errors from all children
  virtual function int unsigned get_total_errors();
    int unsigned total = error_count;
    
    foreach (checkers[i]) begin
      if ($cast(checker_composite, checkers[i]))
        total += checkers[i].get_total_errors();  // Recursive
      else if (checkers[i].get_type_name() == "protocol_checker")
        total += protocol_checker'(checkers[i]).get_errors();
    end
    
    return total;
  endfunction
  
  // Composite operation: enable/disable all
  virtual function void set_enabled(bit en);
    foreach (checkers[i]) begin
      checker_composite cc;
      if ($cast(cc, checkers[i]))
        cc.set_enabled(en);  // Propagate
      // Also set local state
    end
  endfunction
  
endclass

Combining Structural Patterns

Real testbenches combine these patterns. Here's a common architecture:

flowchart TB
    subgraph Test
        T[Test]
    end
    
    subgraph Facade["Facade Layer"]
        VS[Virtual Sequencer]
    end
    
    subgraph Composite["Composite Structure"]
        ENV[SoC Env]
        subgraph PCIE["PCIe Subsystem"]
            PA1[RP Agent]
            PA2[EP Agent]
        end
        subgraph USB["USB Subsystem"]
            UA[Host Agent]
        end
    end
    
    subgraph Adapter["Adapter Layer"]
        ADP[VIP Adapter]
        VENDOR[Vendor VIP]
    end
    
    T --> VS
    VS --> ENV
    ENV --> PCIE
    ENV --> USB
    USB --> ADP
    ADP --> VENDOR
    
    style VS fill:#fef3c7,stroke:#f59e0b
    style ADP fill:#dbeafe,stroke:#3b82f6
    style ENV fill:#d1fae5,stroke:#10b981
// Complete pattern combination example
class soc_env extends uvm_env;  // Composite
  `uvm_component_utils(soc_env)
  
  // Facade
  soc_virtual_sequencer v_seqr;
  
  // Composite children
  pcie_subsystem_env pcie_subsys;
  usb_env            usb_env_i;
  
  // Adapter for third-party VIP
  usb_vip_adapter    usb_adapter;
  
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    
    // Build composite children
    pcie_subsys = pcie_subsystem_env::type_id::create("pcie_subsys", this);
    
    // Build adapter (wraps vendor VIP internally)
    usb_adapter = usb_vip_adapter::type_id::create("usb_adapter", this);
    
    // Build facade
    v_seqr = soc_virtual_sequencer::type_id::create("v_seqr", this);
  endfunction
  
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    
    // Connect facade to subsystems
    v_seqr.pcie_seqr = pcie_subsys.endpoint_agent.seqr;
    v_seqr.usb_seqr = usb_adapter.seqr;  // Uses adapter's sequencer
    
    // Connect adapter output to scoreboard
    usb_adapter.ap.connect(scoreboard.usb_export);
  endfunction
  
endclass

Common Mistakes

1. Adapter That Leaks Abstraction

// BAD: Adapter exposes vendor internals
class bad_adapter extends uvm_component;
  vendor_monitor vendor_mon;  // Public - leaks abstraction
  
  // Clients start using vendor_mon directly, defeating the adapter
endclass

// GOOD: Adapter hides everything
class good_adapter extends uvm_component;
  protected vendor_monitor vendor_mon;  // Hidden
  uvm_analysis_port #(your_transaction) ap;  // Only your interface exposed
endclass

2. Facade That Does Too Much

// BAD: Facade becomes a god class
class bloated_vseqr extends uvm_sequencer;
  // Too many responsibilities
  task run_full_dma_test();     // Test logic doesn't belong here
  task configure_all_agents();  // Configuration doesn't belong here
  task check_final_status();    // Checking doesn't belong here
endclass

// GOOD: Facade only provides access and coordination
class focused_vseqr extends uvm_sequencer;
  // Access to sub-sequencers
  cpu_sequencer cpu_seqr;
  dma_sequencer dma_seqr;
  
  // Lightweight coordination services
  uvm_barrier sync_point;
  uvm_event_pool events;
endclass

3. Composite With Inconsistent Interface

// BAD: Different methods at different levels
class leaf_agent extends uvm_agent;
  task do_reset(); /* ... */ endtask
endclass

class subsystem_env extends uvm_env;
  task perform_reset(); /* ... */ endtask  // Different name!
endclass

// GOOD: Uniform interface through hierarchy
interface class resettable;
  pure virtual task reset();
endclass

class leaf_agent extends uvm_agent implements resettable;
  virtual task reset(); /* ... */ endtask
endclass

class subsystem_env extends uvm_env implements resettable;
  virtual task reset(); /* ... */ endtask  // Same interface
endclass

4. Breaking Composite Transparency

// BAD: Client must know whether it's dealing with leaf or composite
task reset_component(uvm_component comp);
  if (comp.get_num_children() > 0) begin
    // It's a composite - handle differently
    foreach (comp.get_children()[i])
      reset_component(comp.get_children()[i]);
  end else begin
    // It's a leaf
    comp.do_reset();
  end
endtask

// GOOD: Uniform treatment - composite handles propagation internally
task reset_component(resettable comp);
  comp.reset();  // Same call works for leaf or composite
endtask

Key Takeaways

  • Adapter: Converts interfaces without modifying original classes—essential for VIP integration
  • Facade: Provides simplified access to complex subsystems—virtual sequencers are classic facades
  • Composite: Enables uniform treatment of individuals and groups—UVM hierarchy is built on this
  • Object Adapter preferred over Class Adapter for flexibility
  • Facades should be thin—provide access and coordination, not business logic
  • Composite operations should work uniformly across the tree
  • Combine patterns: Adapter at boundaries, Facade for coordination, Composite for structure

Further Reading

  • Design Patterns, Chapter 4 (Structural Patterns) - Gamma, Helm, Johnson, Vlissides
  • Head First Design Patterns, Chapter 7 (Adapter and Facade) - Freeman & Robson
  • Pattern-Oriented Software Architecture Vol. 1 - Buschmann et al.
  • UVM 1.2 Class Reference - Component Hierarchy

Interview Corner

Q: When would you use Adapter vs Facade?

A: Adapter converts one interface to another—use it when you have incompatible interfaces that need to work together (like integrating third-party VIP). Facade provides a simplified interface to a complex subsystem—use it when you want to hide complexity from clients (like virtual sequencers hiding multiple agents).

Q: What makes UVM's component hierarchy a Composite pattern?

A: UVM components can contain other components (children), and operations like phase methods work uniformly whether called on a leaf (driver) or composite (environment). The test doesn't need to know whether it's dealing with an individual component or an entire hierarchy—the same interface works for both.


Previous: Creational Patterns: Factory and Builder | Next: Behavioral Patterns I (Coming Soon)

Return to Software Engineering for DV

Author
Mayur Kubavat
VLSI Design and Verification Engineer sharing knowledge about SystemVerilog, UVM, and hardware verification methodologies.

Comments (0)

Leave a Comment