Encapsulation:

In short: Encapsulation means keeping a class’s data and the code that changes it together, and controlling who may touch that data from outside. In SystemVerilog you do this with access qualifiers: members are public by default (open to everyone), local limits access to methods of the same class only, and protected works like local but is also visible to child classes. You expose a safe set of methods and hide the internal state behind them.

Encapsulation is one of the core ideas of object-oriented programming. It bundles a class’s properties (its data) with the methods that operate on that data, then puts a wall around the data so the outside world can only reach it through methods you choose to expose. This protects the internal state from being set to nonsense by unrelated code, and it lets you change the internals later without breaking everyone who uses the class.

A simple way to picture it

Think of an ATM. You never reach inside and grab the cash drawer yourself. You interact through a small set of buttons: check balance, withdraw, deposit. The machine validates every request before it touches the money. The cash and the account balance are hidden; only safe operations are exposed. A well-encapsulated class is the same: the data is sealed away, and the public methods are the buttons that guard every change.

The three access levels in SystemVerilog

SystemVerilog gives class members one of three visibility levels. By default a member is public.

QualifierWho can access itVisible to child classes?
public (default)Any code, inside or outside the classYes
protectedOnly methods of the same class and its child classesYes
localOnly methods of the same classNo

The rule of thumb: make data local or protected, and expose behaviour through public methods. Reach for protected when a child class needs the member, and local when even children should be kept out.

A worked example: a bank account class

Here the balance is hidden. Outside code cannot set it directly; it must go through deposit and withdraw, which check the rules first. This is encapsulation doing its job.

class bank_account;
  local int unsigned balance;      // hidden: no direct outside access
  protected string owner;          // child classes may read/set this

  function new(string owner, int unsigned opening = 0);
    this.owner   = owner;
    this.balance = opening;
  endfunction

  // Public methods are the only doors into the balance
  function void deposit(int unsigned amount);
    balance += amount;
  endfunction

  function bit withdraw(int unsigned amount);
    if (amount > balance) begin
      $display("[%s] declined: insufficient funds", owner);
      return 0;             // rule enforced inside the class
    end
    balance -= amount;
    return 1;
  endfunction

  function int unsigned get_balance();
    return balance;         // read-only view, no direct write
  endfunction
endclass
module tb;
  initial begin
    bank_account acc = new("Asha", 100);
    acc.deposit(50);
    void'(acc.withdraw(120));
    $display("balance = %0d", acc.get_balance());
    // acc.balance = 9999;   // illegal: balance is local, compile error
  end
endmodule

Running this prints the guarded result. The withdraw of 120 is allowed because the balance is 150 after the deposit, leaving 30:

# Simulation output
balance = 30
# If the deposit were skipped, the withdraw of 120 would be declined:
# [Asha] declined: insufficient funds

The commented line would fail to compile because balance is local. That is the point: the class controls every change, so the balance can never be set to an invalid value from outside.

local vs protected, shown in code

The difference between local and protected only shows up when you inherit. A child class can see protected members but not local ones.

class parent;
  local     int secret = 1;   // parent only
  protected int shared = 2;   // parent and children
endclass

class child extends parent;
  function void show();
    // int x = secret;   // ERROR: local is not visible to child
    int y = shared;      // OK: protected is inherited
    $display("shared = %0d", y);
  endfunction
endclass

The child reads the protected member and prints it. The line that reads the local member is commented out because it would not compile:

# Simulation output
shared = 2
# Uncommenting "int x = secret;" gives a compile error:
# local member is not visible to child class

So pick protected when a subclass genuinely needs the member, and local when you want the tightest possible seal. In UVM you see this constantly: component fields are often protected so extended drivers or monitors can reach them.

Why encapsulation matters in verification

Testbenches are large and shared across teams. Encapsulation keeps them safe to build on:

  • Prevents accidental corruption. A config object with a hidden field cannot be set to an illegal value by a stray line in a test.
  • Lets internals change freely. As long as the public methods keep the same behaviour, you can rewrite the private parts without breaking users.
  • Makes intent clear. The public methods are the supported interface; everything else is off-limits by design.
  • Protects third-party VIP. Vendors hide internal state and expose only the methods they want you to use.

Expected output, in plain words

In the bank account example, the deposit raises the balance to 150, the withdraw of 120 succeeds and leaves 30, so the display prints a balance of 30. If you tried the withdraw before the deposit with too little money, you would see the “declined: insufficient funds” line and the balance would stay unchanged. Since I review code against the language rules rather than run a simulator here, run it in your own tool or on EDA Playground to confirm the exact log.

Common mistakes to avoid

  • Making everything public. If any code can write your fields, you lose all the protection. Default to hidden and expose only what is needed.
  • Expecting a child to reach a local member. local is invisible to subclasses; use protected if inheritance needs it.
  • Exposing data through a public field instead of a method. A public getter lets you add checks or change storage later; a public field locks you in.
  • Forgetting the default is public. A member with no qualifier is fully open, which is easy to overlook in a large class.
  • Over-sealing. Marking a member local when a subclass legitimately needs it just forces awkward workarounds; protected is often the right middle ground.

Keep learning

Encapsulation is one of four object-oriented pillars. See how the others work in inheritance in SystemVerilog, and study copying objects safely in shallow copy vs deep copy. For where classes fit in a testbench, read the UVM testbench architecture. Find more in our SystemVerilog guides and practice with interview questions.

Frequently asked questions

Encapsulation is one of the four OOP pillars. The next one to read is polymorphism, which shows how one base handle can call the right method at run time.

What is encapsulation in SystemVerilog?

Encapsulation is bundling a class’s data with the methods that act on it, and controlling access to that data from outside. In SystemVerilog you use the local and protected qualifiers to hide members and expose behaviour through public methods.

What is the difference between local and protected?

A local member is accessible only inside methods of the same class and is not visible to child classes. A protected member is also accessible from child classes. Both block direct access from unrelated outside code.

Are class members public by default in SystemVerilog?

Yes. If you do not add local or protected, a member is public and can be read and written by any code that has a handle to the object. Add a qualifier when you want to restrict access.

Why should I hide class data behind methods?

Hiding data lets the class check every change, so it can never be set to an invalid value from outside. It also lets you change how the data is stored later without breaking code that uses the public methods.

Can a child class access a parent’s local member?

No. local members are not visible to subclasses. If a subclass needs the member, declare it protected instead, which is inherited while still being hidden from unrelated code.

How is encapsulation used in UVM?

UVM components often mark internal fields as protected so extended drivers, monitors, or agents can reach them while outside code cannot. Config objects also hide fields and expose set and get style methods to keep values valid.

Similar Posts

One Comment

Comments are closed.