Encapsulation in SystemVerilog

Encapsulation is about protecting your class data from unwanted access. It ensures that internal implementation details are hidden, and only specific methods can modify the data. This makes your code safer and easier to maintain.

What is Encapsulation?

Encapsulation means bundling data (variables) and the methods that work on that data into a single unit (class), and restricting direct access to some of the object's components.

Think of encapsulation like an ATM machine. You can deposit money, withdraw money, and check your balance through the interface. But you can't directly access the cash vault inside. The machine "encapsulates" the money and only allows controlled access.

Why Use Encapsulation?

  • Data Protection - Prevent accidental or malicious modification
  • Controlled Access - Use methods to validate data before changes
  • Flexibility - Change internal implementation without affecting users
  • Debugging - Easier to find bugs when data changes through known methods

Access Modifiers in SystemVerilog

SystemVerilog provides three access modifiers to control visibility of class members:

Modifier Visibility Use Case
(default) Accessible from anywhere Public interface methods
local Only within the same class Private implementation details
protected Same class + child classes Data for inheritance hierarchies

Default is Public!

Unlike C++ or Java, SystemVerilog class members are public by default. You must explicitly use local or protected to restrict access.

The local Keyword

The local keyword makes a member accessible only within the declaring class. Even child classes cannot access it.

Using local
class BankAccount;
    local int balance;       // Only this class can access
    local string pin;
    
    function new(string initial_pin);
        balance = 0;
        pin = initial_pin;
    endfunction
    
    // Public method to deposit (controlled access)
    function void deposit(int amount);
        if (amount > 0) begin
            balance += amount;
            $display("Deposited %0d. New balance: %0d", amount, balance);
        end else begin
            $error("Invalid deposit amount!");
        end
    endfunction
    
    // Public method to withdraw (with validation)
    function bit withdraw(int amount, string entered_pin);
        if (entered_pin != pin) begin
            $error("Wrong PIN!");
            return 0;
        end
        if (amount > balance) begin
            $error("Insufficient balance!");
            return 0;
        end
        balance -= amount;
        $display("Withdrew %0d. Remaining: %0d", amount, balance);
        return 1;
    endfunction
    
    function int get_balance(string entered_pin);
        if (entered_pin == pin)
            return balance;
        else begin
            $error("Wrong PIN!");
            return -1;
        end
    endfunction
endclass

// Usage
BankAccount acc = new("1234");
acc.deposit(1000);           // OK - public method
acc.withdraw(500, "1234");   // OK - correct PIN
// acc.balance = 999999;     // ERROR! balance is local

The protected Keyword

The protected keyword allows access from the declaring class and all classes that extend it. It's useful when child classes need to access parent's internal data.

Using protected
class Transaction;
    protected bit [31:0] addr;   // Child classes can access
    protected bit [31:0] data;
    local int internal_id;       // Only this class can access
    
    function new();
        internal_id = $urandom();
    endfunction
    
    virtual function void display();
        $display("Transaction: addr=0x%h, data=0x%h", addr, data);
    endfunction
endclass

class WriteTransaction extends Transaction;
    bit [3:0] strobe;
    
    function void set_write(bit [31:0] a, bit [31:0] d, bit [3:0] s);
        addr = a;           // OK - protected, accessible in child
        data = d;           // OK - protected
        strobe = s;
        // internal_id = 0;  // ERROR! local is not accessible
    endfunction
    
    virtual function void display();
        $display("WRITE: addr=0x%h, data=0x%h, strobe=0x%h", 
                 addr, data, strobe);
    endfunction
endclass

When to Use protected vs local?

Use protected when child classes need to access or modify the data. Use local when data should be completely private to the class.

Getter and Setter Methods

A common encapsulation pattern is to make data private and provide public methods to get and set values. This allows validation and controlled access.

Getter and Setter Pattern
class Config;
    local int timeout;
    local int max_retries;
    
    // Constructor with defaults
    function new();
        timeout = 100;
        max_retries = 3;
    endfunction
    
    // Getter for timeout
    function int get_timeout();
        return timeout;
    endfunction
    
    // Setter with validation
    function void set_timeout(int t);
        if (t > 0 && t < 10000) begin
            timeout = t;
        end else begin
            $warning("Invalid timeout %0d. Must be 1-9999.", t);
        end
    endfunction
    
    // Getter for max_retries
    function int get_max_retries();
        return max_retries;
    endfunction
    
    // Setter with validation
    function void set_max_retries(int r);
        if (r >= 1 && r <= 10) begin
            max_retries = r;
        end else begin
            $warning("Invalid retries %0d. Must be 1-10.", r);
        end
    endfunction
endclass

// Usage
Config cfg = new();
cfg.set_timeout(500);           // OK
cfg.set_timeout(-1);            // Warning, value not changed
$display("Timeout: %0d", cfg.get_timeout());

Quick Summary

  • Encapsulation = hiding internal data and providing controlled access
  • (default) = public, accessible from anywhere
  • local = private, only accessible within the same class
  • protected = accessible in same class and child classes
  • Use getter/setter methods for controlled data access
  • Combine with validation to ensure data integrity

What's Next?

Continue learning about SystemVerilog: