How to use clone() and copy() in SystemVerilog

3 min reading

When developing a verification environment using SystemVerilog and the UVM methodology, I often encounter the need to either clone a class instance using the clone() method or copy its data using the copy() method. Gaining a clear understanding of how the clone() and copy() methods work is essential for building a functional and robust testbench.

For demonstration purposes, the examples in this article will be based on the following classes:

class c1 extends uvm_object;
  int a;
  `uvm_object_utils(c1)

  function new(string name="c1");
    super.new(name);
  endfunction
endclass

class c2 extends uvm_object;
  int aa;
  c1 c1_inst;
  `uvm_object_utils(c2)

  function new(string name="c2");
    super.new(name);    
  endfunction
endclass

Flavours of the copy() method

When we use the copy action we take the values from the right hand side and store them in the object from the left hand side.

Shallow copy

A copy of an object may be called shallow copy if we copy only the scalar variables from that class and keep the same reference to the sub-objects.

In a shallow copy, we have 2 variables referencing two distinct objects but containing the same data and pointing to the same sub-objects.

Shallow copy example – Same sub-object reference:
module top;
  c2 m_source, m_dest;
  
  initial begin
    m_source=new();
    m_source.c1_inst = new();
    
    m_source.aa = 10;
    m_source.c1_inst.a = 100;
    
    // Shallow copy
    m_dest = new m_source;
    
    m_source.aa = 20;
    m_source.c1_inst.a = 200;

    `uvm_info("M_SOURCE", $sformatf("m_source.aa=%0d, m_source.c1_inst.a=%0d", m_source.aa, m_source.c1_inst.a), UVM_LOW);
    `uvm_info("M_DEST", $sformatf("m_dest.aa=%0d, m_dest.c1_inst.a=%0d", m_dest.aa, m_dest.c1_inst.a), UVM_LOW);

  end
endmodule

Output:

[M_SOURCE] m_source.aa=20, m_source.c1_inst.a=200
[M_DEST] m_dest.aa=10, m_dest.c1_inst.a=200

A shallow copy could also mean copying the scalar variables, but converting all the sub-objects into NULL variables.

Shallow copy example – Null sub-object:
module top;
  c2 m_source, m_dest;
  
  initial begin
    m_source=new();
    m_source.c1_inst = new();
    
    m_source.aa = 10;
    m_source.c1_inst.a = 100;
    
    // Shallow copy
    assert($cast(m_dest, m_source.clone()));
    
    m_source.aa = 20;
    m_source.c1_inst.a = 200;

    `uvm_info("M_SOURCE", $sformatf("m_source.aa=%0d, m_source.c1_inst.a=%0d", 
                                    m_source.aa, m_source.c1_inst.a), UVM_LOW);
    `uvm_info("M_DEST", $sformatf("m_dest.aa=%0d, m_dest.c1_inst.a=%0s", 
                                    m_dest.aa, m_dest.c1_inst == null? "NULL" : "not null"), UVM_LOW);
  end
endmodule

Output:

[M_SOURCE] m_source.aa=20, m_source.c1_inst.a=200
[M_DEST] m_dest.aa=0, m_dest.c1_inst.a=NULL

Deep copy

A deep copy makes use of 2 variables. It references two distinct objects and it contains the exact same data down including every object inside the class.

By default, SystemVerilog only supports shallow copy. You have to define the behavior of clone() and deep copy. UVM supports clone() and copy(), as long as you register the fields with the factory or you implement the do_copy() function.

Deep copy example – Registering with factory:
class c1 extends uvm_object;
  int a;
  
// ---------------------------------------------------------------------
// Registering the class fields with the factory
// ---------------------------------------------------------------------
// Registering the fields with the uvm_factory also results in a deep copy.
// By using UVM_DEFAULT, the factory is informed that the field can be
// copied (among other things)
  `uvm_object_utils_begin(c1)
 	`uvm_field_int(a, UVM_DEFAULT)
  `uvm_object_utils_end

  function new(string name="c2");
    super.new(name);    
  endfunction
endclass
class c2 extends uvm_object;
  int aa;
  c1 c1_inst;
  
// ---------------------------------------------------------------------
// Registering the class fields with the factory
// ---------------------------------------------------------------------
// Registering the fields with the uvm_factory also results in a deep copy.
// By using UVM_DEFAULT, the factory is informed that the field can be
// copied (among other things)
  `uvm_object_utils_begin(c2)
 	`uvm_field_int(aa, UVM_DEFAULT)
  	`uvm_field_object(c1_inst, UVM_DEFAULT)
  `uvm_object_utils_end
  function new(string name="c2");
    super.new(name);    
  endfunction
endclass


module top;
  c2 m_source, m_dest;
  
  initial begin
    m_source=new();
    m_source.c1_inst = new();
    
    m_source.aa = 10;
    m_source.c1_inst.a = 100;
    
    // Deep copy
    assert($cast(m_dest, m_source.clone()));
    
    m_source.aa = 20;
    m_source.c1_inst.a = 200;

    `uvm_info("M_SOURCE", $sformatf("m_source.aa=%0d, m_source.c1_inst.a=%0d", 
                                    m_source.aa, m_source.c1_inst.a), UVM_LOW);
    `uvm_info("M_DEST", $sformatf("m_dest.aa=%0d, m_dest.c1_inst.a=%0d", 
                                    m_dest.aa, m_dest.c1_inst.a), UVM_LOW);
  end
endmodule

Output:

[M_SOURCE] m_source.aa=20, m_source.c1_inst.a=200
[M_DEST] m_dest.aa=10, m_dest.c1_inst.a=100
Deep copy example – Overriding do_copy():
class c1 extends uvm_object;
  int a;
  `uvm_object_utils(c1)
  
  function new(string name="c1");
    super.new(name);
  endfunction

// ---------------------------------------------------------------------
//  Overriding the do_copy() function
// ---------------------------------------------------------------------
// Overriding the do_copy function like this will result in a deep copy
  virtual function void do_copy(uvm_object rhs);
     c1 rhs_;
     $cast(rhs_, rhs);
     this.a = rhs_.a;
  endfunction
endclass

class c2 extends uvm_object;
  int aa;
  c1 c1_inst;
  
  `uvm_object_utils(c2)
  
  function new(string name="c2");
    super.new(name);    
  endfunction
 
// ---------------------------------------------------------------------
//  Overriding the do_copy() function
// ---------------------------------------------------------------------
// Overriding the do_copy function like this will result in a deep copy
  virtual function void do_copy(uvm_object rhs);
     c2 rhs_;
     $cast(rhs_, rhs);
     this.aa = rhs_.aa;
     $cast(this.c1_inst, rhs_.c1_inst.clone());
  endfunction
endclass


module top;
  c2 m_source, m_dest;
  
  initial begin
    m_source=new();
    m_source.c1_inst = new();
    
    m_source.aa = 10;
    m_source.c1_inst.a = 100;
    
    // Deep copy
    assert($cast(m_dest, m_source.clone()));
    
    m_source.aa = 20;
    m_source.c1_inst.a = 200;

    `uvm_info("M_SOURCE", $sformatf("m_source.aa=%0d, m_source.c1_inst.a=%0d", 
                                    m_source.aa, m_source.c1_inst.a), UVM_LOW);
    `uvm_info("M_DEST", $sformatf("m_dest.aa=%0d, m_dest.c1_inst.a=%0d", 
                                    m_dest.aa, m_dest.c1_inst.a), UVM_LOW);
  end
endmodule

Output:

[M_SOURCE] m_source.aa=20, m_source.c1_inst.a=200
[M_DEST] m_dest.aa=10, m_dest.c1_inst.a=100

The method clone()

The clone() method internally calls both the create() and copy() functions to produce a new object. Since clone() returns a value of type uvm_object, it’s typically used together with $cast. This is necessary when you are working with derived classes. You need to cast the returned object to the appropriate type. You can find more information on casting on our SystemVerilog CheatSheet.

Conclusion

By understanding how clone() and copy() mechanisms work, you can avoid subtle bugs and write more predictable, maintainable testbenches. Whether you’re duplicating configuration objects or managing object instances, having a firm grasp on copying behavior gives you much more control over your UVM environment.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Subscribe to our newsletter

Do you want to be up to date with our latest articles?