SystemVerilog/OOP + Interfaces

Object-Oriented Programming & Interfaces

Master classes, inheritance, polymorphism, and interface design for scalable SystemVerilog testbenches.

🏗️ Core OOP Concepts

Classes & Objects

Fundamental

Foundation of object-oriented programming in SystemVerilog for testbench development.

Key Topics:

  • Class declaration and instantiation
  • Constructor and destructor methods
  • Properties and methods
  • Access modifiers (local, protected)
  • Static members and methods

Syntax:

// Basic class structure
class packet;
  // Properties
  rand bit [7:0] data;
  rand bit [3:0] addr;
  local bit [15:0] checksum;
  
  // Constructor
  function new();
    data = 8'h00;
    addr = 4'h0;
  endfunction
  
  // Methods
  function void display();
    $display("Packet: addr=%0h, data=%0h", addr, data);
  endfunction
  
  function bit [15:0] calc_checksum();
    return data + addr;
  endfunction
  
  // Static method
  static function packet create_default();
    packet p = new();
    return p;
  endfunction
endclass

Complete Example:

// Complete packet class example
class ethernet_packet;
  // Random properties
  rand bit [47:0] dest_addr;
  rand bit [47:0] src_addr;
  rand bit [15:0] ethertype;
  rand byte payload[];
  
  // Constraints
  constraint payload_size {
    payload.size() inside {[64:1500]};
  }
  
  constraint valid_ethertype {
    ethertype inside {16'h0800, 16'h0806, 16'h86DD};
  }
  
  // Constructor
  function new(string name = "ethernet_packet");
    this.name = name;
  endfunction
  
  // Methods
  virtual function void post_randomize();
    $display("[%s] Generated packet: DA=%h, SA=%h", 
             name, dest_addr, src_addr);
  endfunction
  
  virtual function bit compare(ethernet_packet pkt);
    return (this.dest_addr == pkt.dest_addr &&
            this.src_addr == pkt.src_addr &&
            this.ethertype == pkt.ethertype);
  endfunction
  
  virtual function ethernet_packet clone();
    ethernet_packet cloned = new();
    cloned.copy(this);
    return cloned;
  endfunction
endclass

Inheritance & Polymorphism

Intermediate

Extend base classes and implement polymorphic behavior for scalable testbench architectures.

Key Topics:

  • Class inheritance with extends
  • Method overriding and virtual functions
  • Abstract classes and pure virtual methods
  • Polymorphism and dynamic binding
  • Super keyword for parent access

Syntax:

// Inheritance syntax
class base_packet;
  rand bit [7:0] length;
  
  virtual function void display();
    $display("Base packet: length=%0d", length);
  endfunction
  
  pure virtual function bit [15:0] calc_crc();
endclass

class tcp_packet extends base_packet;
  rand bit [15:0] src_port;
  rand bit [15:0] dest_port;
  
  // Override parent method
  virtual function void display();
    super.display(); // Call parent
    $display("TCP: src_port=%0d, dest_port=%0d", 
             src_port, dest_port);
  endfunction
  
  // Implement pure virtual
  virtual function bit [15:0] calc_crc();
    return src_port ^ dest_port ^ length;
  endfunction
endclass

Complete Example:

// Complete inheritance example
virtual class base_transaction;
  protected int transaction_id;
  protected time timestamp;
  static protected int next_id = 0;
  
  function new();
    transaction_id = next_id++;
    timestamp = $time;
  endfunction
  
  pure virtual function void print();
  pure virtual function base_transaction clone();
  
  virtual function int get_id();
    return transaction_id;
  endfunction
endclass

class cpu_transaction extends base_transaction;
  rand bit [31:0] addr;
  rand bit [31:0] data;
  rand bit write;
  
  constraint addr_align {
    addr[1:0] == 2'b00; // Word aligned
  }
  
  function new();
    super.new();
  endfunction
  
  virtual function void print();
    $display("[%0t] CPU Transaction ID=%0d: %s addr=%h data=%h",
             timestamp, transaction_id, 
             write ? "WRITE" : "READ", addr, data);
  endfunction
  
  virtual function base_transaction clone();
    cpu_transaction cloned = new();
    cloned.addr = this.addr;
    cloned.data = this.data;
    cloned.write = this.write;
    return cloned;
  endfunction
endclass

class dma_transaction extends base_transaction;
  rand bit [31:0] src_addr;
  rand bit [31:0] dest_addr;
  rand int transfer_size;
  
  constraint size_limit {
    transfer_size inside {[1:1024]};
    transfer_size % 4 == 0; // Word transfers
  }
  
  virtual function void print();
    $display("[%0t] DMA Transaction ID=%0d: src=%h dest=%h size=%0d",
             timestamp, transaction_id, src_addr, dest_addr, transfer_size);
  endfunction
  
  virtual function base_transaction clone();
    dma_transaction cloned = new();
    cloned.src_addr = this.src_addr;
    cloned.dest_addr = this.dest_addr;
    cloned.transfer_size = this.transfer_size;
    return cloned;
  endfunction
endclass

Interfaces & Modports

Advanced

Create clean, reusable interface definitions with modports for different perspectives.

Key Topics:

  • Interface declaration and instantiation
  • Modport definitions for different views
  • Clocking blocks for synchronous designs
  • Interface methods and tasks
  • Parameterized interfaces

Syntax:

// Basic interface structure
interface cpu_bus_if (input logic clk);
  logic [31:0] addr;
  logic [31:0] data;
  logic        write;
  logic        valid;
  logic        ready;
  
  // Clocking block
  clocking cb @(posedge clk);
    output addr, data, write, valid;
    input  ready;
  endclocking
  
  // Modports
  modport master (clocking cb, output valid);
  modport slave  (input addr, data, write, valid,
                  output ready);
  modport monitor (input addr, data, write, valid, ready);
endinterface

Complete Example:

// Complete interface example with methods
interface axi4_lite_if #(
  parameter ADDR_WIDTH = 32,
  parameter DATA_WIDTH = 32
)(input logic aclk, input logic aresetn);

  // Write address channel
  logic [ADDR_WIDTH-1:0] awaddr;
  logic [2:0]            awprot;
  logic                  awvalid;
  logic                  awready;
  
  // Write data channel  
  logic [DATA_WIDTH-1:0] wdata;
  logic [DATA_WIDTH/8-1:0] wstrb;
  logic                  wvalid;
  logic                  wready;
  
  // Write response channel
  logic [1:0]            bresp;
  logic                  bvalid;
  logic                  bready;
  
  // Read address channel
  logic [ADDR_WIDTH-1:0] araddr;
  logic [2:0]            arprot;
  logic                  arvalid;
  logic                  arready;
  
  // Read data channel
  logic [DATA_WIDTH-1:0] rdata;
  logic [1:0]            rresp;
  logic                  rvalid;
  logic                  rready;

  // Clocking blocks
  clocking master_cb @(posedge aclk);
    output awaddr, awprot, awvalid, wdata, wstrb, wvalid;
    output bready, araddr, arprot, arvalid, rready;
    input  awready, wready, bresp, bvalid;
    input  arready, rdata, rresp, rvalid;
  endclocking
  
  clocking slave_cb @(posedge aclk);
    input  awaddr, awprot, awvalid, wdata, wstrb, wvalid;
    input  bready, araddr, arprot, arvalid, rready;
    output awready, wready, bresp, bvalid;
    output arready, rdata, rresp, rvalid;
  endclocking
  
  clocking monitor_cb @(posedge aclk);
    input awaddr, awprot, awvalid, awready;
    input wdata, wstrb, wvalid, wready;
    input bresp, bvalid, bready;
    input araddr, arprot, arvalid, arready;
    input rdata, rresp, rvalid, rready;
  endclocking

  // Modports
  modport master (clocking master_cb);
  modport slave  (clocking slave_cb);
  modport monitor (clocking monitor_cb);
  
  // Interface methods
  task automatic write_transaction(
    input [ADDR_WIDTH-1:0] addr,
    input [DATA_WIDTH-1:0] data,
    input [DATA_WIDTH/8-1:0] strb = '1
  );
    // Write address phase
    master_cb.awaddr <= addr;
    master_cb.awprot <= 3'b000;
    master_cb.awvalid <= 1'b1;
    
    // Write data phase
    master_cb.wdata <= data;
    master_cb.wstrb <= strb;
    master_cb.wvalid <= 1'b1;
    
    // Wait for address ready
    do @(master_cb); while (!master_cb.awready);
    master_cb.awvalid <= 1'b0;
    
    // Wait for data ready
    do @(master_cb); while (!master_cb.wready);
    master_cb.wvalid <= 1'b0;
    
    // Wait for response
    master_cb.bready <= 1'b1;
    do @(master_cb); while (!master_cb.bvalid);
    master_cb.bready <= 1'b0;
    
    if (master_cb.bresp != 2'b00)
      $error("Write transaction failed with response: %0d", master_cb.bresp);
  endtask
  
  task automatic read_transaction(
    input  [ADDR_WIDTH-1:0] addr,
    output [DATA_WIDTH-1:0] data,
    output [1:0] resp
  );
    // Read address phase
    master_cb.araddr <= addr;
    master_cb.arprot <= 3'b000;
    master_cb.arvalid <= 1'b1;
    master_cb.rready <= 1'b1;
    
    // Wait for address ready
    do @(master_cb); while (!master_cb.arready);
    master_cb.arvalid <= 1'b0;
    
    // Wait for read data
    do @(master_cb); while (!master_cb.rvalid);
    data = master_cb.rdata;
    resp = master_cb.rresp;
    master_cb.rready <= 1'b0;
    
    if (resp != 2'b00)
      $error("Read transaction failed with response: %0d", resp);
  endtask
  
  // Assertions
  property write_addr_stable;
    @(posedge aclk) disable iff (!aresetn)
    awvalid && !awready |=> $stable(awaddr) && $stable(awprot);
  endproperty
  
  property write_data_stable;
    @(posedge aclk) disable iff (!aresetn)
    wvalid && !wready |=> $stable(wdata) && $stable(wstrb);
  endproperty
  
  assert_write_addr_stable: assert property(write_addr_stable);
  assert_write_data_stable: assert property(write_data_stable);

endinterface

🎨 Common Design Patterns

Factory Pattern

Create objects without specifying exact classes

Use Case:

Dynamic test generation

Benefit:

Flexible object creation

Example:

class transaction_factory;
  static function base_transaction create(string type);
    case (type)
      "CPU": return new cpu_transaction();
      "DMA": return new dma_transaction();
      default: return null;
    endcase
  endfunction
endclass

Observer Pattern

Notify multiple objects about state changes

Use Case:

Coverage and monitoring

Benefit:

Loose coupling

Example:

class scoreboard;
  mailbox #(transaction) mb;
  
  task run();
    transaction tr;
    forever begin
      mb.get(tr);
      check_transaction(tr);
    end
  endtask
endclass

Strategy Pattern

Encapsulate algorithms and make them interchangeable

Use Case:

Different test scenarios

Benefit:

Runtime algorithm selection

Example:

virtual class test_strategy;
  pure virtual task execute();
endclass

class random_test extends test_strategy;
  virtual task execute();
    // Random stimulus generation
  endtask
endclass

Command Pattern

Encapsulate requests as objects

Use Case:

Test sequencing

Benefit:

Undo/redo, queuing

Example:

virtual class command;
  pure virtual task execute();
  pure virtual task undo();
endclass

class write_command extends command;
  bit [31:0] addr, data;
  virtual task execute();
    // Perform write
  endtask
endclass

🔧 Interface Best Practices

Use Modports

Define clear directional views for different components

Implementation:

modport master (output req, input ack);
modport slave  (input req, output ack);

Clocking Blocks

Synchronize interface signals to clock edges

Implementation:

clocking cb @(posedge clk);
  output data, valid;
  input  ready;
endclocking

Interface Methods

Encapsulate common operations within interfaces

Implementation:

task write(input [31:0] addr, data);
  @(cb) cb.addr <= addr;
  cb.data <= data;
  cb.valid <= 1;
  wait(cb.ready);
endtask

Parameterization

Make interfaces reusable with parameters

Implementation:

interface bus_if #(
  parameter ADDR_WIDTH = 32,
  parameter DATA_WIDTH = 32
)(input logic clk);

⚡ Benefits of OOP in Verification

Code Reusability

Inherit and extend existing classes

60-80% code reduction

Modularity

Encapsulate related data and methods

Easier maintenance

Polymorphism

Single interface, multiple implementations

Flexible designs

Abstraction

Hide implementation details

Cleaner interfaces

Ready to Build Advanced Testbenches?

Apply OOP principles and interface design to create scalable, maintainable verification environments.