Let's see a real-world verification example. Imagine you have different types of
transactions: read, write, and burst transactions. They all need to be printed
and compared, but each has its own format.
class Transaction;
rand bit [31:0] addr;
rand bit [31:0] data;
// Virtual methods - child classes will override these
virtual function void display();
$display("Transaction: addr=0x%h, data=0x%h", addr, data);
endfunction
virtual function bit compare(Transaction t);
return (this.addr == t.addr) && (this.data == t.data);
endfunction
endclass
class ReadTransaction extends Transaction;
rand bit [3:0] burst_len;
virtual function void display();
$display("READ Transaction: addr=0x%h, burst=%0d", addr, burst_len);
endfunction
endclass
class WriteTransaction extends Transaction;
rand bit [3:0] strobe;
virtual function void display();
$display("WRITE Transaction: addr=0x%h, data=0x%h, strobe=0x%h",
addr, data, strobe);
endfunction
endclass
// Array of base class handles
Transaction trans_queue[$];
// Add different transaction types to the same queue
ReadTransaction rd = new();
WriteTransaction wr = new();
rd.randomize();
wr.randomize();
trans_queue.push_back(rd);
trans_queue.push_back(wr);
// Print all transactions - each calls its own display()!
foreach(trans_queue[i]) begin
trans_queue[i].display(); // Correct method called automatically
end
What's Happening Here?
Even though trans_queue is an array of Transaction handles,
when we call display(), SystemVerilog looks at the actual object type
and calls the right version. This is polymorphism in action!