SystemVerilog Data Types Explained with Examples

Every variable you write in hardware description or verification answers two questions: what set of values can it hold, and how is it stored and interpreted. That is exactly what a data type fixes. Verilog gave you a handful of choices and a lot of surprises. SystemVerilog widened the set and made the intent explicit, which is why mastering SystemVerilog data types is the first real step toward writing clean RTL and reliable testbenches.

This page builds the type system from first principles: the value set (2-state versus 4-state), the storage layout (packed versus unpacked), and the tools to name and group types (typedef, struct, union). Every runnable example below was simulated before publishing.

The value set: 2-state vs 4-state

Digital hardware is not just ones and zeros while you design it. A wire can be unknown before reset, and a tri-state bus can be left floating. SystemVerilog keeps two families of types to model this honestly.

  • 4-state types can hold 0, 1, x (unknown) and z (high-impedance). The workhorse is logic. It replaces Verilog’s old reg/wire split for most uses because the compiler infers whether it needs continuous or procedural drive.
  • 2-state types hold only 0 and 1. Examples are bit, byte, int and longint. They start at 0, simulate a little faster, and use less memory, which matters in large testbenches.

The practical difference shows up at time zero and at any point an unknown would appear. A 4-state variable starts as x; a 2-state variable starts as 0. When you copy a value containing x or z into a 2-state variable, those bits silently collapse to 0, so you can lose a genuine unknown. The example makes both effects visible.

module tb;
  logic [3:0] four_state;   // 4-state: starts X
  bit   [3:0] two_state;    // 2-state: starts 0

  initial begin
    $display("logic (uninitialised) = %b", four_state);
    $display("bit   (uninitialised) = %b", two_state);

    four_state = 4'b10xz;
    $display("logic holds X/Z        = %b", four_state);

    two_state = four_state;   // X and Z collapse to 0 when copied into 2-state
    $display("bit after copy         = %b", two_state);

    if (four_state === 4'b10xz)
      $display("=== match: X/Z compared exactly");

    $finish;
  end
endmodule
logic (uninitialised) = xxxx
bit   (uninitialised) = 0000
logic holds X/Z        = 10xz
bit after copy         = 1000
=== match: X/Z compared exactly
/var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpsx6miiee/two_vs_four_state.sv:18: $finish called at 0 (1s)

Notice the === operator in the example. Ordinary == returns x if either side has an unknown bit, so it cannot be used to test for x. The case-equality operators === and !== compare x and z literally, which is why they appear constantly in assertion and checker code. A good rule for beginners: use logic for anything that touches the DUT or its pins, and reach for 2-state types inside scoreboards, counters and loop variables where an unknown would be a bug anyway.

The integer types: byte, int, longint and friends

SystemVerilog adds C-like fixed-width integer types so you do not have to keep writing bit ranges. They are all 2-state and, by default, signed (except time). Signedness is the detail that trips people up, because an out-of-range assignment wraps around in two’s complement rather than saturating.

TypeWidthStatesDefault sign
bit1 (or vector)2-stateunsigned
logic1 (or vector)4-stateunsigned
byte82-statesigned
shortint162-statesigned
int322-statesigned
longint642-statesigned
integer324-statesigned

Because byte is a signed 8-bit type, its range is -128 to 127. Store 200 into it and you get -56, since 200 does not fit and the top bit becomes the sign bit. Add unsigned to the declaration and the same bits read as 200. The example also shows a signed 64-bit overflow, where the largest positive value plus one wraps to the most negative value.

module tb;
  byte          sb;   // signed 8-bit, range -128..127
  byte unsigned ub;   // unsigned 8-bit, range 0..255
  int           i;    // signed 32-bit
  longint       l;    // signed 64-bit

  initial begin
    sb = 8'd200;      // 200 does not fit in a signed byte -> wraps
    ub = 8'd200;      // fits in an unsigned byte
    $display("byte          200 -> %0d", sb);
    $display("byte unsigned 200 -> %0d", ub);

    i = 32'h8000_0000;             // MSB set -> most negative int
    $display("int  0x80000000   -> %0d", i);

    l = 64'sd9223372036854775807;  // largest signed longint
    $display("longint max       -> %0d", l);
    l = l + 1;                     // signed overflow wraps to the minimum
    $display("longint max + 1   -> %0d", l);

    $finish;
  end
endmodule
byte          200 -> -56
byte unsigned 200 -> 200
int  0x80000000   -> -2147483648
longint max       -> 9223372036854775807
longint max + 1   -> -9223372036854775808
/var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpsc_axlei/integer_types.sv:21: $finish called at 0 (1s)

The lesson for interviews and real code: pick width and signedness on purpose. Use int for loop counters and array indices, byte or explicit logic vectors for packet fields, and always state unsigned when a quantity cannot be negative.

Packed vs unpacked arrays

Arrays in SystemVerilog come in two layouts, and the dimension’s position decides which you get. Dimensions written before the name are packed; dimensions written after the name are unpacked.

  • logic [7:0] data; is a packed vector: eight contiguous bits you can slice (data[3:0]), treat as a number, and map directly onto a hardware bus.
  • logic mem [256]; is an unpacked array: 256 separate one-bit elements with no guaranteed adjacency. This is how you model memories and register files.
  • You can combine them: logic [7:0] ram [1024]; is 1024 bytes, each byte packed, the collection unpacked.

The rule of thumb: packed when the whole thing is one value you do arithmetic or bit operations on; unpacked when it is a collection of independent values. Only packed arrays can be assigned as a single vector or passed through a port as bits.

Naming and grouping types: typedef, struct, union

Repeating logic [31:0] across a file invites mistakes. typedef gives a type one name you declare once and reuse. Combine it with struct to bundle related fields, and the design reads like the spec.

A struct packed stores its members as one contiguous vector, first member in the most significant bits. Because it is packed, the whole struct behaves like a sliceable vector and can cross a module port. A union packed overlays several views on the same storage, so you can decode one word either as named fields or as raw bits. The example assembles an instruction through its fields, then re-reads the same 32 bits after writing the flat view.

module tb;
  // typedef names a reusable type. A packed struct lays its fields out
  // as one contiguous vector, MSB-first in declaration order.
  typedef struct packed {
    logic [7:0]  opcode;
    logic [7:0]  addr;
    logic [15:0] data;
  } instr_t;

  // A packed union views the same 32 bits two ways at once.
  typedef union packed {
    instr_t      f;    // as named fields
    logic [31:0] raw;  // as one flat vector
  } word_t;

  word_t w;

  initial begin
    // Write through the field view, read back through the flat view.
    w.f.opcode = 8'hA5;
    w.f.addr   = 8'h10;
    w.f.data   = 16'hBEEF;
    $display("assembled raw word = 0x%08h", w.raw);

    // Write the flat view, read back as decoded fields.
    w.raw = 32'h12_34_5678;
    $display("decoded: opcode=0x%02h addr=0x%02h data=0x%04h",
             w.f.opcode, w.f.addr, w.f.data);

    // The union lets us slice the same storage as a plain vector.
    $display("top byte via slice = 0x%02h", w.raw[31:24]);

    $finish;
  end
endmodule
assembled raw word = 0xa510beef
decoded: opcode=0x12 addr=0x34 data=0x5678
top byte via slice = 0x12
/var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpqdsedfzy/struct_union.sv:33: $finish called at 0 (1s)

Because opcode is declared first, it occupies bits 31:24, so writing 0x12345678 into raw decodes to opcode 0x12. That predictable ordering is the whole point of a packed struct: it is a documented bit layout, not just a loose record. An unpacked struct (just struct without packed) is also useful in testbenches to group transaction fields when you do not care about bit layout, but it cannot be treated as a single vector.

Quick reference

You want to…Use
Model a wire or register touching the DUTlogic
A fast flag or counter that can never be unknownbit, int
A fixed-width signed/unsigned integerbyte, int, longint
A single sliceable bus valuepacked vector / struct packed
A memory or register fileunpacked array
One name for a reused typetypedef
Two interpretations of the same bitsunion packed

Internalise three ideas and the rest follows: 4-state models reality while 2-state runs fast, packed layout is one value while unpacked is a collection, and typedef turns repetition into one clear name.

Quick check

What does an uninitialised logic hold, and what does a bit hold?

A logic variable is 4-state and starts at x (unknown). A bit variable is 2-state and starts at 0. This is why unknowns propagate through logic but disappear in bit.

Why does assigning 200 to a byte print -56?

byte is a signed 8-bit type with range -128 to 127. The value 200 does not fit, so the top bit becomes the sign bit and the two’s-complement interpretation is -56. Declaring byte unsigned would print 200.

What is the difference between a packed and an unpacked array?

A packed array stores bits contiguously, so it can be sliced, used in arithmetic, and passed as a single vector. An unpacked array is a collection of independent elements (good for memories) and cannot be treated as one vector.

When would you use a packed union?

When the same storage must be read two ways, such as decoding a 32-bit word either as named instruction fields or as raw bits. A packed union keeps a consistent, documented bit layout across all its views.

Have questions about this topic?
Share your question in comments or talk to our mentor team for batch guidance.
▶ Try it in the ChipXpert Lab

Compile and simulate this in the browser — no install. Edit the code and re-run instantly.

Open in Verification Lab

Learn Verilog with mentors, labs and placement support

This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.

Explore VLSI Courses
Next batch starts 18th November 2026
Start your VLSI career with ChipXpert

Live online and classroom batches in Hyderabad & Bengaluru. Fill in your details and a counsellor will call you back.

  • Real EDA tools in your browser: industry-standard EDAReal EDA tools in your browser
  • Recorded sessions on the elearn portalRecorded sessions
  • Placement assistance: resume, mock interviewsPlacement assistance
  • Merit scholarship up to 60% · EMI optionsScholarship up to 60%
Hi! Ask me about courses, fees, batches or discounts. ×
BENGALURU
HYDERABAD