A struct and a class both group related data together, but they solve genuinely different problems. A struct is a plain, synthesizable value type for organizing data — nothing more. A class is a reference type built for verification, bundling data together with behavior, inheritance, and dynamic construction. Confusing the two — especially around assignment and equality — is a common source of subtle bugs.

10 min read | Intermediate Level

What You'll Learn

  • The core distinction: a struct is a value type, a class is a reference type
  • Why assigning one struct to another copies its fields, while assigning one class handle to another just shares the same object
  • Why comparing two structs with == checks their contents, while comparing two class handles checks identity
  • Which features — methods, constructors, inheritance — exist on a class but not a struct, and why only one of the two is synthesizable

At a Glance

structclass
Type categoryValue typeReference type (handle)
Assignment (a = b)Copies every fieldCopies the handle; both point to the same object
Equality (a == b)Compares field valuesCompares handle identity (same object?)
Methods (functions/tasks)Not supportedSupported
ConstructorNone — initialized directlynew()
Inheritance (extends)Not supportedSupported
Access control (local/protected)Not supportedSupported
SynthesizableYes, especially when packedNo — simulation and testbench only
MemoryExists wherever declaredAllocated dynamically via new(), garbage collected once unreferenced

Assignment: Copy vs Share

This is the single most important difference, and the one most likely to cause a real bug if missed:


typedef struct {
  int addr;
} packet_t;

class Packet;
  int addr;
endclass

module tb;
  packet_t s1, s2;
  Packet   c1, c2;

  initial begin
    s1.addr = 10;
    s2 = s1;          // struct assignment COPIES every field
    s2.addr = 99;
    $display ("s1.addr=%0d, s2.addr=%0d", s1.addr, s2.addr);

    c1 = new (); c1.addr = 10;
    c2 = c1;          // class assignment copies only the HANDLE
    c2.addr = 99;
    $display ("c1.addr=%0d, c2.addr=%0d", c1.addr, c2.addr);
  end
endmodule

This prints s1.addr=10, s2.addr=99 for the struct — changing s2 after the assignment has no effect on s1, since s2 got its own independent copy of every field the moment s2 = s1; ran. For the class, it prints c1.addr=99, c2.addr=99 — c2 = c1; only copied the handle, so c1 and c2 point to the exact same object, and changing one changes both. See SystemVerilog Copying Objects for how to actually get an independent copy of a class object when you need one.

Equality: Comparing Values vs Comparing Identity

This follows directly from the same value-vs-reference distinction, and it's the part most likely to catch someone off guard:


typedef struct {
  int addr;
  int data;
} packet_t;

class Packet;
  int addr;
  int data;
endclass

module tb;
  packet_t s1, s2;
  Packet   c1, c2;

  initial begin
    // Two SEPARATE structs, with identical field values
    s1.addr = 10; s1.data = 20;
    s2.addr = 10; s2.data = 20;

    // Two SEPARATE class objects, also with identical property values
    c1 = new (); c1.addr = 10; c1.data = 20;
    c2 = new (); c2.addr = 10; c2.data = 20;

    if (s1 == s2)
      $display ("Structs are equal");
    else
      $display ("Structs are NOT equal");

    if (c1 == c2)
      $display ("Classes are equal");
    else
      $display ("Classes are NOT equal");
  end
endmodule

This prints Structs are equal, then Classes are NOT equal — even though c1 and c2 hold exactly the same data. == on a struct compares every field's value; == on a class handle only checks whether both handles reference the same object in memory. c1 and c2 are two genuinely separate objects that simply happen to contain matching data — as far as == is concerned, that doesn't count as equal.

Quick Check

Q: If the code were changed to c2 = c1; instead of constructing c2 separately, would c1 == c2 now return true?

Show Answer

Yes. c2 = c1; makes both handles point to the exact same object, so the identity check == performs succeeds — it's not that the data happens to match anymore, it's that there's only one object for both handles to compare against.

Methods, Constructors, and Inheritance

A struct is data only — it can't have a method, a constructor, or a base type:


// Not legal - a struct cannot contain a method
typedef struct {
  int addr;
  // function void display(); ... endfunction   <- not allowed
} packet_t;

A class supports all of this: methods (covered in SystemVerilog Functions), a constructor (see SystemVerilog Class Constructor), and inheritance via extends (see SystemVerilog Inheritance). If you find yourself wanting to bundle behavior together with data — or reuse a data structure across a hierarchy of related types — that's the signal you need a class, not a struct.

Synthesizability

A struct — especially a packed struct with a well-defined bit layout — is fully synthesizable and commonly used to describe register fields or bus transaction layouts directly in RTL. A class is never synthesizable, under any circumstances; it exists purely for simulation and testbench code.

Where Does Each One Fit?

A packed struct is a natural fit for describing a fixed bit-field layout that both the design and the testbench need to agree on — a bus transaction's raw header format, for instance. A class fits a rich testbench transaction object that needs randomization, methods, and reuse across a family of related transaction types through inheritance — something a struct simply has no mechanism for.

Common Beginner Mistakes

Mistake #1: Assuming struct assignment shares data the way class assignment does

As the first example shows, these behave oppositely. Why? A struct is a value type, so assignment always copies every field independently; a class handle is a reference, so assignment only copies the reference itself — expecting one to behave like the other is a common source of confusing, hard-to-trace bugs.

Mistake #2: Assuming class equality checks field contents

As the equality example shows, == on two class handles checks identity, not content. Why? Two separately constructed objects with identical data are still considered unequal — if you actually need to compare a class object's contents field by field, you have to write that comparison logic yourself.

Mistake #3: Trying to add a method directly to a struct

A struct can only hold data members — no functions, no tasks. Why? If you need behavior bundled together with data, that's exactly what a class is for; a struct simply has no mechanism for it.

Mistake #4: Assuming a class can be used anywhere a struct can

A class is never synthesizable, regardless of what it contains. Why? If the same data structure needs to exist in both the design and the testbench — or in synthesizable RTL at all — it has to be a struct (typically packed), not a class.

Practice Exercise

Challenge: Predict Three Comparisons

Given two structs with identical field values, two separately-constructed class objects with identical property values, and a third class handle assigned directly from one of those two objects (c3 = c1;), predict the result of: s1 == s2, c1 == c2, and c1 == c3.

Hint

Only one of these three comparisons depends on whether the two sides are the exact same object in memory — the other two questions have already been answered directly in this article.

Solution

s1 == s2 is true — struct equality compares field values, and they match. c1 == c2 is false — two separate objects, even with matching data, aren't the same object. c1 == c3 is true — c3 = c1; made c3 point to the exact same object as c1, so the identity check succeeds.

Summary

In this tutorial, you learned:

  • A struct is a value type; a class is a reference type accessed through a handle
  • Assigning one struct to another copies every field independently; assigning one class handle to another only shares the same underlying object
  • == on structs compares field values; == on class handles compares identity — whether both sides reference the same object, not whether their data matches
  • Only a class supports methods, a constructor, and inheritance; a struct is data only
  • A struct (especially packed) is synthesizable; a class is never synthesizable, and exists purely for simulation and testbench code
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5