SystemVerilog interviews are often more practical than candidates expect. Knowing the syntax of logic, always_ff, classes, constraints, or assertions is useful, but interviewers frequently ask candidates to write code, modify existing code, identify a bug, or explain what the code actually does.
For RTL design and functional verification roles, coding questions can range from simple sequential logic to SystemVerilog-specific features such as interfaces, queues, randomization, constraints, assertions, functional coverage, and object-oriented programming.
The important point is that an interviewer is usually evaluating more than whether the code compiles. They want to know whether you understand hardware behavior, simulation semantics, synthesizability, corner cases, and verification intent.
This article covers practical SystemVerilog coding questions commonly relevant to VLSI interviews, with examples and the kinds of follow-up questions you should expect.
This is one of the simplest questions, but it tests whether you understand sequential logic and SystemVerilog procedural blocks.
module dff (
input logic clk,
input logic rst_n,
input logic d,
output logic q
);
always_ff @(posedge clk) begin
if (!rst_n)
q <= 1’b0;
else
q <= d;
end
endmodule
The interviewer may immediately ask:
Why did you use always_ff and <=?
always_ff clearly communicates that the block represents flip-flop-based sequential logic. The nonblocking assignment models the simultaneous update behavior expected from sequential elements.
They may then change the requirement:
“Make the reset asynchronous.”
The sensitivity list would become:
always_ff @(posedge clk or negedge rst_n)
The important skill is not memorizing both versions. You should be able to modify the code when the specification changes.
A common coding exercise is to write a counter that can be configured for different widths.
module counter #(
parameter int WIDTH = 8
) (
input logic clk,
input logic rst_n,
input logic enable,
output logic [WIDTH-1:0] count
);
always_ff @(posedge clk) begin
if (!rst_n)
count <= ‘0;
else if (enable)
count <= count + 1’b1;
end
endmodule
The interviewer may ask:
For an N-bit unsigned counter, normal binary arithmetic naturally wraps when the maximum representable value is exceeded.
A good candidate should also recognize that parameterization makes the RTL reusable rather than hard-coding an 8-bit implementation.
This tests basic combinational RTL coding.
module mux2 (
input logic a,
input logic b,
input logic sel,
output logic y
);
always_comb begin
if (sel)
y = b;
else
y = a;
end
endmodule
A follow-up question may be:
“Why use always_comb instead of always @(*)?”
always_comb is a SystemVerilog construct specifically intended for combinational logic. It makes design intent clearer and provides additional language-level checking compared with traditional Verilog procedural blocks.
The interviewer may then intentionally remove the else statement:
always_comb begin
if (sel)
y = b;
end
You should identify the problem: when sel is false, y is not assigned in that procedural execution, potentially causing latch inference.
This type of modification question is often more valuable than simply asking you to write a mux from scratch.
Consider:
always_ff @(posedge clk) begin
q1 = d;
q2 = q1;
end
An interviewer may ask whether this is correct.
The preferred sequential coding style is:
always_ff @(posedge clk) begin
q1 <= d;
q2 <= q1;
end
With nonblocking assignments, both registers update based on their values before the clock event.
Therefore:
Before clock:
q1 = 0
q2 = 0
d = 1
After clock:
q1 = 1
q2 = 0
On the next clock, q2 can receive the previous value of q1.
This is a classic interview question because it tests whether you understand simulation scheduling and sequential hardware behavior, rather than simply knowing assignment syntax.
Finite-state-machine coding is another common RTL exercise.
For example, suppose an FSM has three states:
IDLE → ACTIVE → DONE
↑ |
└──────────────┘
A SystemVerilog implementation can separate state storage from next-state logic.
typedef enum logic [1:0] {
IDLE,
ACTIVE,
DONE
} state_t;
state_t state, next_state;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= IDLE;
else
state <= next_state;
end
always_comb begin
next_state = state;
case (state)
IDLE: begin
if (start)
next_state = ACTIVE;
end
ACTIVE: begin
if (complete)
next_state = DONE;
end
DONE: begin
next_state = IDLE;
end
default:
next_state = IDLE;
endcase
end
The interviewer may ask:
This question tests coding style, FSM understanding, reset behavior, and completeness of combinational assignments.
SystemVerilog coding interviews for verification roles often move beyond RTL into constrained randomization.
Suppose you need to generate an 8-bit address between 0 and 255, but only even addresses are allowed.
class transaction;
rand bit [7:0] addr;
constraint addr_c {
addr[0] == 1’b0;
}
endclass
The least significant bit of an even number is zero, so this constraint restricts the generated addresses accordingly.
You could also express a range:
constraint addr_range {
addr inside {[8’h20:8’h7F]};
}
A likely follow-up question is:
What happens if the constraints conflict?
For example:
constraint c1 {
addr inside {[0:10]};
}
constraint c2 {
addr > 20;
}
There is no valid value satisfying both constraints, so randomization fails.
A verification engineer should know how to detect and debug such situations rather than assuming randomize() always succeeds.
SystemVerilog supports object-oriented programming, which is particularly important in verification environments.
class packet;
bit [7:0] addr;
bit [31:0] data;
function new(bit [7:0] addr,
bit [31:0] data);
this.addr = addr;
this.data = data;
endfunction
function void display();
$display(“ADDR = %0h DATA = %0h”, addr, data);
endfunction
endclass
An interviewer may ask:
Why use this.addr instead of simply addr?
this refers to the current object. Here, it distinguishes the class property from the constructor argument having the same name.
They may then ask you to create an object:
packet p;
initial begin
p = new(8’h20, 32’h1234_ABCD);
p.display();
end
This is a basic example, but it tests whether you understand object creation, constructors, class properties, and methods.
Interfaces are frequently discussed in verification-oriented SystemVerilog interviews because they group related signals and simplify communication between the DUT and testbench.
interface bus_if(input logic clk);
logic valid;
logic ready;
logic [31:0] data;
endinterface
The interface can then be connected to a DUT or used by verification components.
A common follow-up question is:
What problem does an interface solve?
Instead of passing many related signals individually between components, the interface groups them into a single reusable communication structure.
SystemVerilog testbench guidance also highlights interfaces as an important way of simplifying signal connectivity in larger verification environments.
For a verification interview, you should also be prepared for questions about modports and virtual interfaces.
Assertions are another important coding area.
Suppose the requirement is:
Whenever req is asserted, ack must be asserted in the next cycle.
A simple concurrent assertion is:
property req_ack;
@(posedge clk)
req |=> ack;
endproperty
assert property (req_ack);
If the requirement instead says that the acknowledgement must occur within one to three cycles, you could express a temporal window:
property req_ack_window;
@(posedge clk)
req |-> ##[1:3] ack;
endproperty
assert property (req_ack_window);
The interviewer may ask about the difference between |-> and |=>.
You should be able to explain that they differ in when the consequent begins relative to the antecedent.
SystemVerilog Assertions are particularly useful for checking temporal behavior such as handshakes, FIFO conditions, pipeline relationships, reset behavior, and protocol rules.
A coding interview for a verification role may ask you to write a small coverage model.
For example:
covergroup bus_cg @(posedge clk);
cp_valid: coverpoint valid;
cp_ready: coverpoint ready;
cp_data: coverpoint data {
bins low = {[0:63]};
bins mid = {[64:191]};
bins high = {[192:255]};
}
endgroup
You can create an instance:
bus_cg cg = new();
The purpose is not merely to prove that the signal changed. Functional coverage should represent meaningful scenarios derived from the verification plan.
For example, if a bus protocol has different operating modes, you may want to cover combinations of those modes rather than simply measuring whether each signal toggled.
SystemVerilog functional coverage uses constructs such as covergroups, coverpoints and bins to describe functional scenarios.
An interviewer may give you a small FIFO problem and ask you to write the empty/full logic.
For a simple counter-based FIFO, the conceptual conditions might be:
assign empty = (count == 0);
assign full = (count == DEPTH);
The interviewer may then change the requirement:
“Can read and write happen in the same cycle?”
That changes the design considerations.
You need to determine:
For example:
always_ff @(posedge clk) begin
case ({wr_en, rd_en})
2’b10: count <= count + 1’b1;
2’b01: count <= count – 1’b1;
default: count <= count;
endcase
end
This is a useful interview exercise because the candidate has to think about boundary conditions and simultaneous events, not just syntax.
Consider:
class packet;
rand bit [7:0] length;
constraint c_length {
length > 10;
length < 5;
}
endclass
The constraints are contradictory.
There is no value that is simultaneously greater than 10 and less than 5.
Therefore:
packet p = new();
if (!p.randomize())
$display(“Randomization failed”);
is an important pattern to understand.
An interviewer may then ask:
How would you debug a randomization failure?
Start by checking:
This type of question tests debugging ability, not just SystemVerilog syntax.
This is a popular SystemVerilog coding discussion because all three are useful but serve different purposes.
int data[];
Its size can be allocated dynamically.
data = new[10];
int q[$];
Queues support operations such as adding and removing elements.
q.push_back(10);
q.push_front(20);
int value;
value = q.pop_front();
int memory[int];
The array is indexed by a key, which can be useful for sparse data structures.
The interviewer may ask:
Which one would you use for a transaction FIFO?
A queue is often a natural choice because transactions can be inserted and removed in order.
The correct answer, however, depends on the actual data structure and access requirements.
For a basic verification environment, an interviewer may ask how you would compare expected and actual data.
A simplified example is:
if (actual_data !== expected_data) begin
$error(“Mismatch: expected=%0h actual=%0h”,
expected_data, actual_data);
end
Notice the use of !== rather than !=.
Four-state simulation includes X and Z, so case inequality can be useful when you want unknown values to participate in the comparison rather than being treated like ordinary two-state values.
A follow-up question could be:
“Why might == produce unexpected behavior when X values are present?”
You should be able to explain the difference between logical equality and case equality in four-state simulation.
After completing an interview coding problem, do not immediately say, “Done.”
Take a few seconds to review your code.
Check:
Did you use blocking and nonblocking assignments appropriately?
Does the reset meet the stated requirement?
Did you assign outputs in every relevant condition?
Could an arithmetic operation overflow or truncate unexpectedly?
What happens at zero, maximum value, empty, full, or simultaneous operations?
Does the RTL describe hardware that can actually be synthesized?
Could race conditions, unknown values, or scheduling semantics affect the result?
Could the code be parameterized or simplified without making it harder to understand?
These checks are consistent with practical RTL interview preparation guidance: candidates are often evaluated on reset behavior, assignment types, latch avoidance, synthesizability, corner cases, and whether they can explain the hardware created by their code.
The best way to prepare is to code rather than simply read SystemVerilog interview questions.
Start with small exercises:
Then move into verification-oriented exercises:
For each problem, follow the same process:
Understand the requirement → identify the hardware or verification behavior → write the simplest implementation → test corner cases → explain your code.
For verification roles, practice writing small SystemVerilog testbench components instead of memorizing UVM code. SystemVerilog testbench development commonly involves drivers, monitors, scoreboards, stimulus generation, assertions, interfaces, and coverage.
You should also practice explaining why you chose a particular construct.
For example, if asked to write a counter, do not stop after producing the code. Explain:
“I used always_ff because this is clocked sequential logic, used a nonblocking assignment for the register update, and used a parameter so the counter width can be changed without rewriting the module.”
That explanation demonstrates considerably more understanding than the code alone.
SystemVerilog coding interviews are designed to test whether you can translate a specification into correct, maintainable RTL or verification code.
The most important areas to practice include:
Do not focus on writing the shortest possible solution. Focus on writing code that clearly communicates your design or verification intent.
A strong interview candidate can write the code, explain why it works, identify its limitations, and modify it when the interviewer changes the requirement.
That is the difference between knowing SystemVerilog syntax and being able to use SystemVerilog effectively in a VLSI engineering environment.