Inheritance in SystemVerilog

Inheritance allows you to create new classes based on existing ones, reusing code and building hierarchies. It's the backbone of UVM's testbench architecture.

Understanding Inheritance

Inheritance is one of the most powerful features of Object-Oriented Programming. It allows you to create a new class (called the child class or derived class) that inherits properties and methods from an existing class (called the parent class or base class).

Think of it this way: You have a generic "Vehicle" class with basic properties like wheels, engine, and speed. Now you want to create specific types like Car, Bike, and Truck. Instead of writing everything from scratch, you just inherit from Vehicle and add specific features.

Real-World Verification Example

In verification, you might have a base Transaction class. From it, you create specialized transactions like ReadTransaction, WriteTransaction, BurstTransaction, etc. Each inherits common features but adds its own specific behavior.

Basic Inheritance Syntax

Use the extends keyword to inherit from a parent class:

Basic Inheritance Example
// ═══════════════════════════════════════════════════════════
// PARENT CLASS (Base Class)
// ═══════════════════════════════════════════════════════════
class Transaction;
    bit [31:0] addr;
    bit [63:0] data;
    bit [3:0]  id;
    
    function new();
        addr = 0;
        data = 0;
        id = 0;
        $display("[Transaction] Base object created");
    endfunction
    
    function void display();
        $display("Address: 0x%08h, Data: 0x%016h, ID: %0d", 
                 addr, data, id);
    endfunction
    
    function bit is_valid();
        return (addr[1:0] == 2'b00);  // Word aligned
    endfunction
endclass

// ═══════════════════════════════════════════════════════════
// CHILD CLASS (Derived from Transaction)
// ═══════════════════════════════════════════════════════════
class WriteTransaction extends Transaction;
    // New properties specific to write transactions
    bit [7:0]  byte_enable;
    bit        exclusive;
    
    function new();
        super.new();  // Call parent constructor FIRST
        byte_enable = 8'hFF;
        exclusive = 0;
        $display("[WriteTransaction] Write object created");
    endfunction
    
    // Override parent's display method
    function void display();
        super.display();  // Call parent's display first
        $display("ByteEnable: 0x%02h, Exclusive: %0b", 
                 byte_enable, exclusive);
    endfunction
endclass

// ═══════════════════════════════════════════════════════════
// ANOTHER CHILD CLASS
// ═══════════════════════════════════════════════════════════
class ReadTransaction extends Transaction;
    bit [3:0] burst_length;
    bit       cacheable;
    
    function new();
        super.new();
        burst_length = 1;
        cacheable = 1;
        $display("[ReadTransaction] Read object created");
    endfunction
    
    function void display();
        super.display();
        $display("BurstLen: %0d, Cacheable: %0b", 
                 burst_length, cacheable);
    endfunction
endclass

Using the Child Classes

Testing Inheritance
module testbench;
    initial begin
        Transaction    txn;
        WriteTransaction wr_txn;
        ReadTransaction  rd_txn;
        
        $display("═══════════════════════════════════════");
        $display(" Creating Base Transaction");
        $display("═══════════════════════════════════════");
        txn = new();
        txn.addr = 32'h1000_0000;
        txn.data = 64'hDEAD_BEEF;
        txn.display();
        
        $display("\n═══════════════════════════════════════");
        $display(" Creating Write Transaction");
        $display("═══════════════════════════════════════");
        wr_txn = new();
        wr_txn.addr = 32'h2000_0000;
        wr_txn.data = 64'hCAFE_BABE;
        wr_txn.byte_enable = 8'h0F;
        wr_txn.exclusive = 1;
        wr_txn.display();
        
        $display("\n═══════════════════════════════════════");
        $display(" Creating Read Transaction");
        $display("═══════════════════════════════════════");
        rd_txn = new();
        rd_txn.addr = 32'h3000_0000;
        rd_txn.burst_length = 8;
        rd_txn.display();
    end
endmodule

The 'super' Keyword

The super keyword is used to access parent class members from the child class. It's essential in two scenarios:

1. Calling Parent Constructor

When you override the constructor, always call super.new() first to initialize parent properties:

Parent Constructor Call
class Parent;
    int x;
    function new(int val);
        x = val;
        $display("Parent: x = %0d", x);
    endfunction
endclass

class Child extends Parent;
    int y;
    
    function new(int val1, int val2);
        super.new(val1);  // MUST call parent constructor first
        y = val2;
        $display("Child: y = %0d", y);
    endfunction
endclass

// Usage:
initial begin
    Child c = new(10, 20);
    // Output:
    // Parent: x = 10
    // Child: y = 20
end

2. Calling Parent Methods

When you override a method but still want to use the parent's implementation:

Calling Parent Method
class BasePacket;
    bit [7:0] header;
    bit [31:0] payload;
    
    function void display();
        $display("=== Packet Info ===");
        $display("Header: 0x%02h", header);
        $display("Payload: 0x%08h", payload);
    endfunction
endclass

class ExtendedPacket extends BasePacket;
    bit [15:0] crc;
    bit [7:0]  sequence_num;
    
    function void display();
        super.display();  // First show base packet info
        // Then show extended info
        $display("CRC: 0x%04h", crc);
        $display("Seq: %0d", sequence_num);
        $display("==================");
    endfunction
endclass

Multi-Level Inheritance

You can create inheritance chains where a child class becomes the parent of another class. This creates a hierarchy of increasing specialization.

Multi-Level Inheritance
// Level 1: Base Transaction
class Transaction;
    bit [31:0] addr;
    bit [63:0] data;
    
    function new();
        $display("Transaction created");
    endfunction
endclass

// Level 2: AXI Transaction (inherits from Transaction)
class AXITransaction extends Transaction;
    bit [3:0]  awid;
    bit [7:0]  awlen;
    bit [2:0]  awsize;
    bit [1:0]  awburst;
    
    function new();
        super.new();
        $display("AXI Transaction created");
    endfunction
endclass

// Level 3: AXI4 Transaction (inherits from AXITransaction)
class AXI4Transaction extends AXITransaction;
    bit [3:0]  awqos;     // Quality of Service (new in AXI4)
    bit [3:0]  awregion;  // Region identifier (new in AXI4)
    
    function new();
        super.new();
        $display("AXI4 Transaction created");
    endfunction
endclass

// Level 4: AXI4-Stream Transaction
class AXI4StreamTransaction extends AXI4Transaction;
    bit [7:0]  tdest;
    bit [3:0]  tuser;
    bit        tlast;
    
    function new();
        super.new();
        $display("AXI4-Stream Transaction created");
    endfunction
endclass

// Testing:
initial begin
    AXI4StreamTransaction stream_txn;
    stream_txn = new();
    
    // Output (constructors called in order):
    // Transaction created
    // AXI Transaction created
    // AXI4 Transaction created
    // AXI4-Stream Transaction created
    
    // Access all inherited properties:
    stream_txn.addr = 32'h1000;     // From Transaction
    stream_txn.awid = 4'h5;         // From AXITransaction
    stream_txn.awqos = 4'hF;        // From AXI4Transaction
    stream_txn.tlast = 1;           // From AXI4StreamTransaction
end

Practical UVM-Style Example

This example shows how UVM uses inheritance to create specialized components. Understanding this pattern is crucial for UVM mastery.

UVM-Style Inheritance Pattern
// ═══════════════════════════════════════════════════════════
// Base Sequence Item (like uvm_sequence_item)
// ═══════════════════════════════════════════════════════════
class base_seq_item;
    static int item_count = 0;
    int item_id;
    
    function new();
        item_count++;
        item_id = item_count;
    endfunction
    
    virtual function void display(string prefix = "");
        $display("%s[Item #%0d]", prefix, item_id);
    endfunction
    
    virtual function base_seq_item copy();
        base_seq_item item = new();
        return item;
    endfunction
    
    virtual function bit compare(base_seq_item other);
        return (this.item_id == other.item_id);
    endfunction
endclass

// ═══════════════════════════════════════════════════════════
// AHB Sequence Item
// ═══════════════════════════════════════════════════════════
class ahb_seq_item extends base_seq_item;
    rand bit [31:0] haddr;
    rand bit [31:0] hwdata;
    rand bit        hwrite;
    rand bit [2:0]  hsize;
    rand bit [2:0]  hburst;
    rand bit [3:0]  hprot;
    
    bit [31:0] hrdata;   // Response
    bit [1:0]  hresp;    // Response status
    
    // Constraints
    constraint addr_align_c {
        (hsize == 3'b001) -> (haddr[0] == 0);      // Halfword aligned
        (hsize == 3'b010) -> (haddr[1:0] == 0);    // Word aligned
        (hsize == 3'b011) -> (haddr[2:0] == 0);    // Double-word aligned
    }
    
    function new();
        super.new();
    endfunction
    
    virtual function void display(string prefix = "");
        super.display(prefix);
        $display("%sHADDR  : 0x%08h", prefix, haddr);
        $display("%sHWRITE : %s", prefix, hwrite ? "WRITE" : "READ");
        $display("%sHSIZE  : %0d bytes", prefix, 2**hsize);
        $display("%sHBURST : %0d", prefix, hburst);
        if (hwrite)
            $display("%sHWDATA : 0x%08h", prefix, hwdata);
        else
            $display("%sHRDATA : 0x%08h", prefix, hrdata);
    endfunction
    
    virtual function base_seq_item copy();
        ahb_seq_item item = new();
        item.haddr  = this.haddr;
        item.hwdata = this.hwdata;
        item.hwrite = this.hwrite;
        item.hsize  = this.hsize;
        item.hburst = this.hburst;
        item.hprot  = this.hprot;
        return item;
    endfunction
endclass

// ═══════════════════════════════════════════════════════════
// AHB Burst Sequence Item (more specialized)
// ═══════════════════════════════════════════════════════════
class ahb_burst_seq_item extends ahb_seq_item;
    rand bit [7:0] burst_length;
    bit [31:0] burst_data[$];  // Queue to hold burst data
    
    constraint valid_burst_c {
        hburst inside {3'b001, 3'b010, 3'b011, 3'b100, 3'b101, 3'b110, 3'b111};
        burst_length >= 1;
        burst_length <= 16;
    }
    
    function new();
        super.new();
        burst_data = {};
    endfunction
    
    virtual function void display(string prefix = "");
        super.display(prefix);
        $display("%sBurst Length: %0d", prefix, burst_length);
        foreach(burst_data[i])
            $display("%s  Data[%0d]: 0x%08h", prefix, i, burst_data[i]);
    endfunction
endclass

// ═══════════════════════════════════════════════════════════
// Test Module
// ═══════════════════════════════════════════════════════════
module test;
    initial begin
        ahb_burst_seq_item burst_txn;
        
        burst_txn = new();
        
        if (!burst_txn.randomize() with { 
            hwrite == 1; 
            burst_length == 4;
            hsize == 3'b010;  // 4 bytes
        })
            $fatal("Randomization failed!");
        
        // Add burst data
        for (int i = 0; i < burst_txn.burst_length; i++)
            burst_txn.burst_data.push_back($urandom());
        
        $display("\n╔════════════════════════════════════════╗");
        $display("║     AHB Burst Write Transaction        ║");
        $display("╚════════════════════════════════════════╝");
        burst_txn.display("  ");
    end
endmodule

Key Rules and Best Practices

Rules to Remember

  • Always call super.new() - If parent has a constructor, call it first in child's constructor
  • SystemVerilog supports single inheritance only - A class can extend only one parent class
  • Child can access parent's public members - Private members are not directly accessible
  • Constructor calls propagate up - In multi-level inheritance, all constructors are called from top to bottom

Best Practices

  • Use inheritance for "is-a" relationships (WriteTransaction IS-A Transaction)
  • Don't create deep inheritance hierarchies (3-4 levels maximum is a good guideline)
  • Use virtual for methods you expect to be overridden
  • Document what methods are meant to be overridden by child classes

Common Interview Questions

  1. What is inheritance and why is it useful?

    Inheritance allows creating new classes based on existing ones, promoting code reuse and establishing hierarchies. Child classes inherit parent's properties and methods while adding their own.

  2. What is the difference between 'super' and 'this'?

    this refers to the current object, while super refers to the parent class. Use super to access parent's constructor or methods that have been overridden.

  3. Does SystemVerilog support multiple inheritance?

    No, SystemVerilog only supports single inheritance. A class can extend only one parent class. However, you can achieve similar functionality using composition (having objects as members).

  4. What happens if you don't call super.new() in child constructor?

    If the parent has a default constructor (no arguments), it's called automatically. If parent constructor requires arguments, you must explicitly call super.new() with required arguments, otherwise it's a compilation error.