Skip to main content

Module 1 — SystemVerilog Basics

VLSI Verification  •  SystemVerilog Series



Module 1 — SystemVerilog Basics

A complete, example-driven walkthrough of SystemVerilog fundamentals: data types, variables vs nets, arrays of every flavor, strings, enums, structs, unions, and typedef — the building blocks every verification engineer needs before UVM.

🧩 Beginner → Intermediate ⏱ ~25 min read 💻 Code Examples Included

1Introduction to SystemVerilog

SystemVerilog (IEEE 1800) is a unified hardware description and verification language (HDVL). It extends Verilog (IEEE 1364) with powerful constructs for design (interfaces, always_comb/always_ff, enhanced data types) and verification (classes, randomization, constraints, functional coverage, assertions).

It was created by merging two industry efforts — Accellera's Superlog/SystemVerilog 3.1 extensions and the OVM/VMM verification methodologies — into a single IEEE standard. Today it is the backbone of nearly every modern ASIC/FPGA verification environment, especially through UVM (Universal Verification Methodology), which is built entirely in SystemVerilog.

📘 Why it matters

SystemVerilog lets one language do both RTL design and testbench verification — no more juggling Verilog for design and a separate language (like Vera or e) for verification.

What SystemVerilog Adds Over Verilog

  • Design side: logic type, always_comb/always_ff/always_latch, interfaces, packages, enhanced literals.
  • Verification side: classes & OOP, randomization (rand/randc), constraints, mailboxes, semaphores, functional coverage (covergroup), assertions (SVA).
  • Data types: 2-state types, dynamic arrays, queues, associative arrays, strings, structs, unions.

2Verilog vs SystemVerilog

Verilog is primarily an RTL/gate-level design language; SystemVerilog is a superset built for both design and verification productivity.

AspectVerilog (IEEE 1364)SystemVerilog (IEEE 1800)
Primary UseRTL designDesign + Verification (HDVL)
Data Typesreg, wire (4-state only)logic, bit, byte, int, shortint, longint (2-state & 4-state)
OOP SupportNoneFull classes, inheritance, polymorphism
RandomizationNot supportedrand, randc, constraint blocks
ArraysFixed-size onlyDynamic, queues, associative arrays
AssertionsNot nativeSVA — immediate & concurrent assertions
InterfacesNot availableinterface construct with modports
Always Blocksalwaysalways_comb, always_ff, always_latch
CoverageNot nativecovergroup, coverpoint, cross coverage
💡 Interview Tip

A common interview question: "Is SystemVerilog backward compatible with Verilog?" Yes — every legal Verilog-2001 construct is legal SystemVerilog, since SV is a strict superset.

3Data Types: 2-State vs 4-State Logic

Verilog's reg and wire are 4-state types: each bit can be 0, 1, X (unknown), or Z (high-impedance). SystemVerilog adds 2-state types that can only hold 0 or 1 — no X or Z.

SystemVerilog
// 4-state types — can hold 0, 1, X, Z logic [7:0] data4; // most common in RTL & TB reg [7:0] legacy_reg; wire [7:0] legacy_wire; integer count4; // 2-state types — can hold ONLY 0, 1 (faster simulation, less memory) bit [7:0] data2; byte b; // 8-bit signed shortint si; // 16-bit signed int i; // 32-bit signed longint li; // 64-bit signed
Feature4-State (logic/reg)2-State (bit/int/byte)
Possible Values0, 1, X, Z0, 1 only
Default InitX0
Simulation SpeedSlowerFaster
Memory UsageHigher (2 bits/bit)Lower (1 bit/bit)
Typical UseRTL signals, DUT portsTestbench counters, scoreboards
Detecting Uninitialized ReadsYes (shows as X)No (silently reads 0)
⚠️ Common Pitfall

Because 2-state variables reset to 0 instead of X, an uninitialized 2-state signal can silently mask a bug that a 4-state logic variable would have flagged as X. Use logic for DUT-facing signals and reserve bit/int for pure testbench bookkeeping.

logic vs reg vs wire

logic merges the roles of reg and single-driver wire — it can be driven by a continuous assignment, a procedural block, or a port connection, but it cannot have multiple drivers (for that you still need wire / tri).

4Variables and Nets

SystemVerilog data objects fall into two categories: nets (represent physical wires connecting structural elements) and variables (represent storage, updated procedurally).

SystemVerilog
// NET types — model physical wires, need continuous drivers wire w1; // default net, resolves multiple drivers tri t1; // same as wire, used for tri-state wand, wor // wired-AND / wired-OR resolution supply0, supply1 // power/ground nets // VARIABLE types — hold values, assigned procedurally logic [31:0] reg_data; // single-driver only int idx; bit flag; // Example: illegal vs legal driving wire a = 1'b1; wire a = 1'b0; // OK for wire — resolves via net resolution (X if conflict) logic b; assign b = 1'b1; // assign b = 1'b0; // ILLEGAL — logic can't have 2 drivers
AspectNet (wire)Variable (logic)
RepresentsPhysical wire/connectionStorage element
Driven ByContinuous assignment / gate / portProcedural block (always/initial) or single continuous assign
Multiple DriversAllowed (resolved by strength/type)Not allowed
Default ValueZ (high-Z)X
Used ForModule interconnectsRegisters, testbench state
📘 Rule of Thumb

Use logic everywhere in RTL and TB except when you genuinely need multi-driver resolution (e.g., a bidirectional bus) — that's when wire/tri is required.

5Arrays: Packed vs Unpacked

SystemVerilog distinguishes between packed arrays (contiguous bits, treated as a single vector) and unpacked arrays (separate storage elements, like a C array).

SystemVerilog
// PACKED array — dimensions BEFORE the variable name // Bits are contiguous; can be treated as one big vector logic [7:0] byte_data; // 1-D packed, 8 bits logic [3:0][7:0] packed_bytes; // 2-D packed: 4 bytes = 32 bits total byte_data = 8'hFF; // treat whole thing as a number packed_bytes = 32'hDEAD_BEEF; // legal — packed dims are bit-vector-like // UNPACKED array — dimensions AFTER the variable name // Each element is a separate storage location logic [7:0] mem_array [0:255]; // 256 elements, each 8 bits int unpacked_ints [10]; // 10 separate int elements mem_array[0] = 8'hAA; // index into unpacked dimension // COMBINED packed + unpacked logic [7:0] mixed_array [0:15]; // 16 elements, each an 8-bit packed vector
AspectPacked ArrayUnpacked Array
Syntax PositionBefore variable name [3:0] dataAfter variable name data [3:0]
Memory LayoutContiguous bits (single vector)Separate elements
Can Cast to IntegerYesNo (not directly)
Typical UseBit-vectors, register fieldsMemories, arrays of structs/objects
Bit-Select Whole ArrayYesNo
💡 Interview Tip

Classic question: "Declare a memory of 1024 words, each 32 bits wide, and explain packed vs unpacked."
Answer: logic [31:0] mem [0:1023]; — [31:0] is packed (word width), [0:1023] is unpacked (number of words/addresses).

6Dynamic Arrays

Dynamic arrays are unpacked arrays whose size can be set and changed at run time using new[]. Unlike fixed arrays, their size isn't known at compile time.

SystemVerilog
int dyn_arr[]; // declare dynamic array (empty, size 0) dyn_arr = new[10]; // allocate 10 elements dyn_arr[0] = 5; dyn_arr[9] = 100; // Resize while preserving old values dyn_arr = new[20](dyn_arr); // grow to 20, keep existing data // Useful built-in methods $display("size = %0d", dyn_arr.size()); dyn_arr.delete(); // clear the array (size becomes 0) // Common use case: sizing based on a packet length field function void alloc_payload(int len); byte payload[]; payload = new[len]; endfunction
📘 Key Methods

size(), delete(), and array locator/reduction methods (sum(), find(), sort(), reverse()) all work on dynamic arrays, since they belong to SystemVerilog's general "array manipulation methods" available on unpacked array types.

⚠️ Gotcha

Calling new[N] without the parenthesized old-array argument wipes existing contents. Always pass the old array — new[N](old_array) — if you need to preserve data while resizing.

7Queues

A queue is a variable-size, ordered collection — like a dynamic array with built-in FIFO/LIFO operations. Declared with $ as the unpacked dimension.

SystemVerilog
int q[$]; // unbounded queue int q_bounded[$:15]; // bounded queue, max 16 elements // Push / pop operations q.push_back(10); // add to tail q.push_front(5); // add to head int x = q.pop_front(); // remove & return from head int y = q.pop_back(); // remove & return from tail // Indexing & slicing (like a dynamic array) q = {1, 2, 3, 4, 5}; q[1] = 20; // modify element int sub[] = q[1:3]; // slice // Insert / delete anywhere q.insert(2, 99); // insert value 99 at index 2 q.delete(0); // remove element at index 0 $display("size=%0d", q.size());
💡 Where Queues Shine

Queues are the go-to structure for scoreboards and transaction mailboxes in UVM-style testbenches — pushing expected transactions on one side and popping/comparing them against actual DUT outputs on the other.

StructureResizeFast Insert/Delete at EndsFast Random Access
Fixed ArrayNoN/AYes
Dynamic ArrayYes (via new[])SlowYes
QueueAutomaticYes (push/pop)Yes
Associative ArrayAutomaticYes (by key)By key only

8Associative Arrays

An associative array is a sparse, key-indexed collection — similar to a hash map/dictionary. Elements are allocated only when written, making it memory-efficient for large or sparse address spaces.

SystemVerilog
// Declaration: data_type array_name [index_type] int assoc[string]; // string-indexed bit [31:0] mem [bit[31:0]]; // sparse memory model, address-indexed // Writing & reading assoc["alice"] = 30; assoc["bob"] = 25; // Existence check — avoids auto-creating an entry if (assoc.exists("alice")) $display("alice = %0d", assoc["alice"]); // Iterating with first/next/last/prev string key; if (assoc.first(key)) do $display("%s -> %0d", key, assoc[key]); while (assoc.next(key)); // Deletion assoc.delete("bob"); // remove one key assoc.delete(); // remove all keys $display("num entries = %0d", assoc.num());
⚠️ Common Pitfall

Directly reading a non-existent key, e.g. y = assoc["missing"], silently returns the default value and creates the entry. Always check with exists() first if you don't want to pollute the array.

9Strings

SystemVerilog's built-in string type is a dynamically-sized class-like type (not a fixed-width bit vector), with a rich set of built-in methods.

SystemVerilog
string s1 = "Hello"; string s2 = "World"; // Concatenation string s3 = {s1, " ", s2}; // "Hello World" // Common built-in methods $display(s1.len()); // 5 $display(s1.toupper()); // "HELLO" $display(s1.tolower()); // "hello" $display(s1.substr(1,3)); // "ell" $display(s1.putc(0, "J")); // replace char at index 0 -> "Jello" $display(s1.getc(0)); // get ASCII code of char 0 $display(s1.compare(s2)); // like C strcmp // Conversion between string and integers int val = s1.atoi(); // string -> integer string num_str; num_str.itoa(42); // integer -> string
📘 Note

Unlike C, SystemVerilog strings don't need manual memory management or a null terminator — they grow/shrink automatically, similar to a queue of bytes under the hood.

10Enumerations

An enum creates a named set of integer constants, improving readability and enabling compile-time type checking versus using raw magic numbers.

SystemVerilog
// Basic enum (default base type is int, starts at 0) typedef enum { IDLE, FETCH, DECODE, EXECUTE, WRITEBACK } state_e; state_e cur_state = IDLE; // Enum with explicit values and explicit base type typedef enum bit[2:0] { OP_ADD = 3'b000, OP_SUB = 3'b001, OP_AND = 3'b010, OP_OR = 3'b011 } opcode_e; // Useful methods cur_state = cur_state.next(); // FETCH cur_state = cur_state.prev(); // back to IDLE $display(cur_state.name()); // prints "IDLE" (string form) $display(cur_state.num()); // number of enum members // Using in a case statement (great with always_comb FSMs) always_comb case (cur_state) IDLE: next_state = FETCH; FETCH: next_state = DECODE; DECODE: next_state = EXECUTE; default: next_state = IDLE; endcase
💡 Interview Tip

Enums are strongly typed — you cannot assign an out-of-range integer to an enum variable without an explicit cast. This is a favorite interview trap: cur_state = 5; is illegal unless cur_state = state_e'(5);.

11Structures

A struct groups multiple, possibly different-typed fields into a single named entity — like a C struct or a bus of related signals.

SystemVerilog
// Unpacked struct (each field independently stored — default) typedef struct { bit [7:0] addr; bit [31:0] data; bit valid; } axi_beat_t; axi_beat_t beat; beat.addr = 8'h10; beat.data = 32'hCAFE_BABE; beat.valid = 1'b1; // Packed struct (contiguous bits — can be cast to a vector) typedef struct packed { bit [3:0] opcode; bit [3:0] operand1; bit [3:0] operand2; } instr_t; instr_t instr; instr = 12'hAB1; // legal — packed struct behaves like a vector // Array of structs — common for building a "transaction" list axi_beat_t beats[10];
📘 Packed vs Unpacked Struct

A packed struct is stored as one contiguous bit vector (fields laid out MSB→LSB in declaration order) and can be directly assigned to/from an integer. An unpacked struct stores fields as separate variables and cannot be cast to a single vector without a manual pack.

12Unions

A union lets multiple fields share the same storage location (size = size of the largest member) — useful for reinterpreting the same bits as different types.

SystemVerilog
// Packed union — all members must be the same size, contiguous bits typedef union packed { bit [31:0] word; bit [3:0][7:0] bytes; // reinterpret the same 32 bits as 4 bytes } data_view_u; data_view_u dv; dv.word = 32'hDEAD_BEEF; $display("byte0 = %h", dv.bytes[0]); // view same bits as a byte array // Tagged union — safer, self-describing variant type typedef union tagged { void Invalid; int Valid; } maybe_int_u; maybe_int_u m; m = tagged Valid (42); case (m) matches tagged Valid .v: $display("value = %0d", v); tagged Invalid: $display("no value"); endcase
⚠️ Watch Out

Ordinary (non-tagged) unions offer no type safety — writing one member and reading another is legal and simply reinterprets the bits. Use tagged unions when you need SystemVerilog to track which member is actually active.

13Typedef

typedef creates a new name (alias) for an existing type — improving readability, enabling reuse, and letting you forward-declare types used before their full definition.

SystemVerilog
// Simple alias typedef logic [31:0] word_t; word_t a, b; // Typedef for arrays typedef byte mem_array_t [0:1023]; mem_array_t rom; // Typedef combined with enum / struct / union (very common pattern) typedef enum { RD, WR } cmd_e; typedef struct packed { cmd_e cmd; logic [31:0] addr; logic [31:0] data; } bus_txn_t; bus_txn_t txn; txn.cmd = WR; txn.addr = 32'h1000; // Forward typedef — needed when a class references itself // or two classes reference each other typedef class transaction; // forward declaration class generator; transaction tr; // usable here even though 'transaction' is defined below endclass class transaction; rand bit [7:0] payload; endclass
💡 Best Practice

Put all shared typedefs (enums, structs, common bus types) in a package and import it wherever needed — this is exactly how UVM-style testbenches share transaction and configuration types across environment, agents, and scoreboard.

🎯 Module 1 Recap

  • SystemVerilog is a unified design + verification language, a strict superset of Verilog.
  • Use logic (4-state) for RTL/DUT signals; use bit/int (2-state) for fast testbench bookkeeping.
  • Nets (wire) model physical wires with multi-driver resolution; variables (logic) model single-driver storage.
  • Packed arrays are contiguous bit-vectors; unpacked arrays are collections of separate elements.
  • Dynamic arrays, queues, and associative arrays give you resizable, insertable, and sparse/keyed storage respectively.
  • Built-in string, enum, struct, and union types make testbench code far more expressive than raw Verilog vectors.
  • typedef is the glue that makes all these types reusable and shareable across a verification environment.

📚 Next up — Module 2: Procedural Constructs & Always Blocks (always_comb, always_ff, always_latch, tasks & functions)

↑ Back to Table of Contents

Comments

Popular posts from this blog

GPU Memory System & Interconnect: How Data Travels Inside a Modern GPU

GPU Memory System & Interconnect: How Data Travels Inside a Modern GPU Modern GPUs deliver incredible computing power by processing thousands of operations simultaneously. While compute cores often receive the spotlight, the real performance of a GPU depends heavily on how efficiently data moves between memory and compute units. A GPU memory system is designed to provide extremely high bandwidth while keeping latency as low as possible. This is achieved through a collection of specialized hardware components working together, including the Memory Controller, HBM/GDDR memory, Crossbar, Network-on-Chip (NoC), DMA Engine, PCIe Interface, and NVLink. In this article, we'll explore each component in detail and understand how they collectively enable high-performance parallel computing. Why Does GPU Memory Matter? Every GPU application—from gaming and graphics rendering to AI model training and scientific simulations—requires continuous movement of enormous amounts of data. For examp...

Clock Domain Crossing (CDC): A Complete Beginner's Guide to Metastability, Synchronizers & Safe Data Transfer

Clock Domain Crossing (CDC): A Complete Beginner's Guide to Metastability, Synchronizers & Safe Data Transfer Modern System-on-Chip (SoC) designs rarely operate with a single clock. Instead, different modules such as CPUs, GPUs, memory controllers, communication interfaces, and peripherals often run at different clock frequencies or even use completely independent clock sources . When data moves from one clock domain to another, timing uncertainty can cause serious problems such as metastability , data corruption , and unpredictable system behaviour . This challenge is known as Clock Domain Crossing (CDC) . CDC is one of the most important topics in ASIC Design , RTL Design , ASIC Verification , FPGA Design , and SoC Architecture . Every modern chip—from smartphones to automotive processors—uses CDC techniques to ensure reliable communication between multiple clock domains. In this article, we'll explore CDC fundamentals, metastability, synchronization techniques, asynchro...

AXI Verification Interview Questions and Real-World Debugging

AMBA Protocol Series · Part 10 of 10 AXI Verification Interview Questions and Real-World Debugging Common protocol violations, how to read them off a waveform, and a curated set of interview questions with concise, correct answers — the capstone of the core 10-day series. AMBA Protocol Series: From APB & AHB to AXI — A Complete Guide for VLSI Engineers This wraps up the core 10-day series. Everything here draws directly on Days 1–9 — if any answer below feels unfamiliar, that's a good signal for which earlier post to revisit. (If you want to go further, see the 20-day extended list at the end of this post.) In This Article Common Protocol Violations Debugging From a Waveform Interview Questions and Answers Series Recap Extending the Series 1. Common Protocol Violations VALID deasserted before handshake — a source drops VALID before READY ever went high (violates the Day 8 stabi...

Cache Basics and Cache Mapping Techniques

  Cache Basics and Cache Mapping Techniques  Modern processors execute billions of instructions every second. However, processor speed has increased much faster than main memory (RAM) speed. This difference, known as the memory wall , creates a performance bottleneck because the CPU often has to wait for data from slower memory. To solve this problem, computer systems use cache memory —a small, high-speed memory located between the CPU and the main memory. In this article, we will understand the fundamentals of cache memory, the principle of locality, cache organization, and the three major cache mapping techniques. What is Cache Memory? Cache memory is a small, high-speed memory that stores copies of frequently accessed data from the main memory. Since accessing cache is much faster than accessing RAM, the CPU first checks whether the required data is available in the cache. If the data is found in the cache, it is called a cache hit . Otherwise, the CPU fetches the dat...

Timing Basics in Static Timing Analysis (STA): Delay Models, Sequential Timing, and Wire Delays

Timing Basics in Static Timing Analysis (STA) Static Timing Analysis (STA) is one of the most important verification techniques used in modern VLSI design. Before a chip is manufactured, engineers must verify that every signal reaches its destination within the required clock period. Even if a design passes functional simulation, it may still fail after fabrication because of timing violations. Unlike simulation, Static Timing Analysis mathematically analyzes every timing path without applying input test vectors. This makes STA significantly faster and capable of analyzing millions of timing paths efficiently. Table of Contents What is Static Timing Analysis? Why STA is Required Propagation Delay Contamination Delay Cell Delay Models Combinational Gate Delay Sequential Timing Setup Time Hold Time Wire Delay RC Delay Model PVT Corners Worked Examples Interview Questions Key Takeaways What is Static Timing Analysis? Static Ti...

Computer Architecture Interview Question Part 1

Skip to content Computer Architecture Interview Series Part 1 · CPU Basics · RISC vs CISC · ISA 50 Most Asked Computer Architecture Interview Questions (Part 1) The foundational round every VLSI, ASIC, and CPU design interview opens with: instruction set architecture, RISC vs CISC trade-offs, Von Neumann vs Harvard design, registers, endianness, and the fetch-decode-execute cycle — explained the way interviewers actually expect you to answer.  40 Questions (Q1–Q40) ⌛ 22 min read  VLSI & Computer Architecture Interviews On this page Foundations of Computer Architecture RISC vs CISC — Core Differences Von Neumann, Harvard & the Semantic Gap Architecture Styles, Endianness & Registers ILP, VLIW, Superscalar & Amdahl's Law Register Files, Microcontrollers & the Fetch Cycle Tip...