A structure can contain elements of different data types, referenced either as a whole or individually by name. This is quite different from an array, where every element shares the same data type — an array might hold ten int values or 256 bit values, but every one of those elements is the same type as its neighbors. A structure instead groups together fields that can each be a completely different type, such as a byte, an int, and a string all in one variable.

15 min read | Intermediate Level

What You'll Learn

  • The difference between a structure (mixed types) and an array (same type), and the default unpacked declaration
  • Why typedef-ing a structure matters once more than one variable of the same shape is needed
  • Why assigning one structure variable to another copies its contents, rather than sharing a reference
  • How packed structures lay out their members as contiguous bits, most-significant member first
  • How to build fixed, dynamic, and associative arrays of structures

Syntax


struct {
	[list of variables]
} struct_name;

Unpacked Structures

A structure is unpacked by default, defined with the struct keyword and a list of member declarations inside curly brackets, followed by the structure's name:

Structure Example


module tb;  
  	// Create a structure called "st_fruit"
	// which to store the fruit's name, count and expiry date in days.
  	// Note: this structure declaration can also be placed outside the module
	struct {
  		string fruit;
  		int    count;
  		byte 	 expiry;  
	} st_fruit;
  
  initial begin
    // st_fruit is a structure variable, so let's initialize it
    st_fruit = '{"apple", 4, 15};
  
    // Display the structure variable
    $display ("st_fruit = %p", st_fruit);
  
    // Change fruit to pineapple, and expiry to 7
    st_fruit.fruit = "pineapple";
    st_fruit.expiry = 7;
    $display ("st_fruit = %p", st_fruit);
  end
endmodule

Output

Output
ncsim> run
st_fruit = '{fruit:"apple", count:4, expiry:'hf}
st_fruit = '{fruit:"pineapple", count:4, expiry:'h7}
ncsim: *W,RNQUIE: Simulation is complete.

Only fruit and expiry were reassigned in the second display — count still shows 4, its original value from the very first assignment. Each member holds its own value independently; changing one never disturbs the others.

Quick Check

Q: Why does count still show 4 in the second $display, when only .fruit and .expiry were explicitly changed?

Show Answer

Because count was never touched after its initial assignment — struct members are independent storage locations, so a value set once persists until something explicitly changes that specific member.

What is the need to typedef a structure?

Only one variable was created in the example above. If there's a need to create multiple structure variables with the same constituents, it's better to create a user-defined data type using typedef — then st_fruit becomes a data type that can be used to declare as many variables of that shape as needed:


module tb;  
  	// Create a structure called "st_fruit"
	// which to store the fruit's name, count and expiry date in days.
  	// Note: this structure declaration can also be placed outside the module
	typedef struct {
  		string fruit;
  		int    count;
  		byte 	 expiry;  
	} st_fruit;
  
  initial begin
    // st_fruit is a data type, so we need to declare a variable of this data type
    st_fruit fruit1 = '{"apple", 4, 15};
    st_fruit fruit2;
  
    // Display the structure variable
    $display ("fruit1 = %p fruit2 = %p", fruit1, fruit2);
  
    // Assign one structure variable to another and print
    // Note that contents of this variable is copied into the other
   	fruit2 = fruit1;
    $display ("fruit1 = %p fruit2 = %p", fruit1, fruit2);
  
    // Change fruit1 to see if fruit2 is affected
    fruit1.fruit = "orange";
    $display ("fruit1 = %p fruit2 = %p", fruit1, fruit2);
  end
endmodule

Output

Output
ncsim> run
fruit1 = '{fruit:"apple", count:4, expiry:'hf} fruit2 = '{fruit:"", count:0, expiry:'h0}
fruit1 = '{fruit:"apple", count:4, expiry:'hf} fruit2 = '{fruit:"apple", count:4, expiry:'hf}
fruit1 = '{fruit:"orange", count:4, expiry:'hf} fruit2 = '{fruit:"apple", count:4, expiry:'hf}
ncsim: *W,RNQUIE: Simulation is complete.

fruit2 starts out with the default values for each member type — an empty string, 0, and 0. After fruit2 = fruit1;, both variables hold identical contents, because the assignment copies every field's value rather than making fruit2 point at the same data. This is confirmed by the final step: changing fruit1.fruit afterward has no effect on fruit2.fruit at all — the two are completely independent once the copy has happened.

Quick Check

Q: Structures behave this way because they're a value type. What would happen instead if st_fruit were a class rather than a struct, and fruit2 = fruit1; assigned one handle to another?

Show Answer

Both handles would then point at the exact same object, so changing fruit1.fruit afterward would also change what fruit2.fruit reports — the opposite of what this example shows. This is exactly the value-vs-reference distinction covered in SystemVerilog Class vs Struct.

Packed Structures

A packed structure subdivides a vector into fields that can be accessed as members, packed together in memory without gaps. The first member declared is the most significant, with subsequent members following in decreasing order of significance.

A structure is declared packed using the packed keyword — otherwise, it's unpacked by default.

Example


// Create a "packed" structure data type which is similar to creating
// bit [7:0]  ctrl_reg;
// ctrl_reg [0]   represents en
// ctrl_reg [3:1] represents cfg
// ctrl_reg [7:4] represents mode
typedef struct packed {
  bit [3:0] mode;
  bit [2:0] cfg;
  bit       en;
} st_ctrl;

module tb;
  st_ctrl    ctrl_reg;
  
  initial begin
    // Initialize packed structure variable
    ctrl_reg = '{4'ha, 3'h5, 1};
    $display ("ctrl_reg = %p", ctrl_reg);
  
    // Change packed structure member to something else
    ctrl_reg.mode = 4'h3;
    $display ("ctrl_reg = %p", ctrl_reg);
  
    // Assign a packed value to the structure variable
    ctrl_reg = 8'hfa;
    $display ("ctrl_reg = %p", ctrl_reg);
  end  
endmodule

Output

Output
ncsim> run
ctrl_reg = '{mode:'ha, cfg:'h5, en:'h1}
ctrl_reg = '{mode:'h3, cfg:'h5, en:'h1}
ctrl_reg = '{mode:'hf, cfg:'h5, en:'h0}
ncsim: *W,RNQUIE: Simulation is complete.

Because mode occupies the top 4 bits, cfg the next 3, and en the bottom bit, assigning the raw value 8'hfa (binary 1111_1010) to the whole struct splits cleanly into mode = 4'hf, cfg = 3'h5, and en = 1'h0 — exactly what the output shows. The struct and the raw 8-bit vector are two views of the exact same bits.

Quick Check

Q: If ctrl_reg were assigned 8'b0000_0001 (only the bottom bit set), what would mode, cfg, and en each show?

Show Answer

mode = 0, cfg = 0, en = 1. Only the bottom bit is set, and that bit belongs entirely to en — the upper 7 bits, covering mode and cfg, are all zero.

Array of Structures

An array — fixed, dynamic, or associative — can hold structures just as easily as it can hold any other type:


// Define the structure
typedef struct {
  int data;
  string name;
} MyStruct;

module array_examples;

  // 1. Fixed Array
  MyStruct fixed_array[5];

  // 2. Dynamic Array
  MyStruct dynamic_array[];

  // 3. Associative Array
  MyStruct associative_array[int];

  initial begin
    $display("--- Fixed Array Example ---");
    // Populate the fixed array
    for (int i = 0; i < 5; i++) begin
      fixed_array[i].data = i * 10;
      fixed_array[i].name = $sformatf("Fixed_%0d", i);
      $display("fixed_array[%0d]: data=%0d, name=%s", i, fixed_array[i].data, fixed_array[i].name);
    end

    $display("
--- Dynamic Array Example ---");
    // Initialize and populate the dynamic array
    dynamic_array = new[3]; // Allocate space for 3 elements

    dynamic_array[0].data = 100;
    dynamic_array[0].name = "Dynamic_A";

    dynamic_array[1].data = 200;
    dynamic_array[1].name = "Dynamic_B";

    dynamic_array[2].data = 300;
    dynamic_array[2].name = "Dynamic_C";

    for (int i = 0; i < dynamic_array.size(); i++) begin
      $display("dynamic_array[%0d]: data=%0d, name=%s", i, dynamic_array[i].data, dynamic_array[i].name);
    end

    $display("
--- Associative Array Example ---");
    // Populate the associative array using keys
    associative_array[10] = '{101, "Assoc_X"};
    associative_array[25] = '{202, "Assoc_Y"};
	associative_array[5]  = '{303, "Assoc_Z"}; // Keys don't need to be in order

    // Iterating through associative array (keys are returned in sorted order)
    $display("Iterating through associative_array:");
    foreach (associative_array[key]) begin
      $display("associative_array[%0d]: data=%0d, name=%s", key, associative_array[key].data, associative_array[key].name);
    end
  end

endmodule

Output

Output
xcelium> run
--- Fixed Array Example ---
fixed_array[0]: data=0, name=Fixed_0
fixed_array[1]: data=10, name=Fixed_1
fixed_array[2]: data=20, name=Fixed_2
fixed_array[3]: data=30, name=Fixed_3
fixed_array[4]: data=40, name=Fixed_4

--- Dynamic Array Example ---
dynamic_array[0]: data=100, name=Dynamic_A
dynamic_array[1]: data=200, name=Dynamic_B
dynamic_array[2]: data=300, name=Dynamic_C

--- Associative Array Example ---
Iterating through associative_array:
associative_array[5]: data=303, name=Assoc_Z
associative_array[10]: data=101, name=Assoc_X
associative_array[25]: data=202, name=Assoc_Y
xmsim: *W,RNQUIE: Simulation is complete.  

The three associative array keys were assigned out of order — 10, then 25, then 5 — but foreach visits them in sorted order (5, 10, 25), not insertion order. Each entry's data still correctly matches whichever key it was assigned under.

Where Is This Used?

Packed structures are the standard way to model a hardware register's bitfields with readable member names, instead of manually slicing and masking bits everywhere the register is touched. Unpacked structures — especially typedef'd ones — are commonly used for testbench transaction or packet objects that bundle several related fields together into one convenient unit.

Common Beginner Mistakes

Mistake #1: Assuming struct assignment shares a reference

As the typedef example shows, it doesn't — assigning one struct variable to another copies every field, leaving the two variables fully independent afterward. Why? This is the opposite of how assigning one class handle to another behaves — struct is a value type, class is a reference type.

Mistake #2: Forgetting the first-declared member of a packed struct is the most significant

It's easy to assume declaration order runs least-significant-first instead. Why? Getting this backward means a packed struct meant to model a real register's bitfields ends up with every field mapped to the wrong bit positions.

Mistake #3: Not accounting for cumulative width when reasoning about field positions

A packed member's exact bit position depends on the widths of every member declared before it, not just its own size. Why? Changing an earlier member's width shifts the position of every member that follows it — worth re-checking the full layout whenever a packed struct's fields change size.

Mistake #4: Assuming associative array iteration follows insertion order

As the array-of-structures example shows, keys 10, 25, and 5 were inserted in that order, but foreach visits them as 5, 10, 25. Why? Associative arrays iterate in sorted key order, regardless of the order elements were actually added.

Practice Exercise

Challenge: Decode a Packed Instruction

Given typedef struct packed { bit [2:0] opcode; bit [4:0] operand; } instr_t;, what would opcode and operand show if the whole struct were assigned 8'hA6?

Hint

opcode is declared first, so it occupies the most significant bits. Convert 0xA6 to binary and split it into the top 3 bits and the bottom 5.

Solution

opcode = 3'b101 (5), operand = 5'b00110 (6). 8'hA6 is 1010_0110 in binary — the top 3 bits (101) belong to opcode, since it's declared first, and the remaining 5 bits (00110) belong to operand.

Summary

In this tutorial, you learned:

  • A structure holds members of different types, referenced as a whole or individually by name; an array holds elements that all share the same type
  • typedef-ing a structure turns it into a reusable data type, useful whenever more than one variable of the same shape is needed
  • Assigning one structure variable to another copies every field — the two variables become fully independent, unlike assigning one class handle to another
  • A packed structure lays its members out as contiguous bits, with the first-declared member occupying the most significant position
  • Arrays of structures work the same way as arrays of any other type, whether fixed, dynamic, or associative — and associative arrays always iterate in sorted key order
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5