DigiFlop

HomeInterview Question Bank › RTL & Verilog Coding
VLSI Interview Prep
VLSI Interview Questions — RTL & Verilog Coding
The coding round every chip-design interview loop has — less about syntax, more about whether your RTL matches the hardware you actually intend to build.
RTL and Verilog coding rounds test something specific: can you write synthesizable code that becomes the hardware you intend, and can you spot the classic bugs — inferred latches, blocking/non-blocking mistakes, off-by-one counters — that separate simulation-only RTL from production-quality RTL. These are the questions candidates report most often, from wire-vs-reg fundamentals through FSM design, parameterized counters, and the debugging questions interviewers use to see how you think out loud.
Q1.What's the difference between `wire` and `reg` in Verilog, and does `reg` always mean the signal becomes a flip-flop?
`wire` represents a signal driven continuously by something else — typically an `assign` statement or a module port connection — and cannot hold a value on its own. `reg` is a variable that can be assigned inside a procedural block (`always` or `initial`) and can retain its last assigned value between assignments — but despite the name, `reg` does NOT automatically mean the signal synthesizes to a flip-flop. Whether a `reg` becomes a flip-flop, a latch, or pure combinational logic depends entirely on how and where it's assigned: a `reg` assigned inside a clocked always block becomes a flip-flop, one assigned inside a fully-specified combinational always block stays purely combinational, and one assigned incompletely in a combinational block becomes an unintended latch.
Q2.What's the difference between blocking (=) and non-blocking (<=) assignments, and when should each be used?
Blocking assignments execute immediately and sequentially within the always block — each statement completes (blocks execution of the next statement) before the next one runs — and are the correct choice for combinational logic, where the whole block re-evaluates instantly on any input change. Non-blocking assignments schedule their update to happen at the end of the current simulation time step, using the pre-update (old) values of every right-hand-side expression, which correctly models real flip-flop behavior where every register updates simultaneously on a clock edge based on values from just before that edge. Using blocking assignments inside a clocked always block is a classic bug source — it can cause a shift register to incorrectly collapse every stage to the same value in a single step instead of correctly shifting one stage per clock, and more generally can create simulation-versus-synthesis mismatches.
Q3.Why must non-blocking assignments be used for the outputs of a state machine's next-state logic?
If a state register update used blocking assignment, and the next-state logic reads the current state and other signals to compute a new value in the same always block, there's a real risk that later statements within that same evaluation could read the just-blocking-assigned new state value instead of the old one — behaving like a same-cycle combinational chain rather than the intended one-state-transition-per-clock-edge behavior. Non-blocking assignment guarantees every read of the current state within that clock edge's evaluation sees the value from before this edge, correctly modeling the fact that real flip-flops all sample simultaneously and only reflect their new value on the following cycle.
Q4.What's the danger of mixing blocking and non-blocking assignments to the same signal within different always blocks?
If the same signal is assigned with blocking logic in one always block and non-blocking logic in another (or even within the same block under different branches), the actual update ordering becomes dependent on simulator-specific event scheduling behavior rather than a well-defined Verilog semantic — different simulators, or even different runs, can produce different results, and synthesis tools may infer completely different hardware than what any particular simulation run showed. The standard rule of thumb — always blocking for combinational logic, always non-blocking for clocked/sequential logic, and never assigning the same signal from more than one always block — exists specifically to avoid this class of ambiguous, hard-to-debug inconsistency.
Q5.Why is always @(posedge clk) preferred over always @(clk) for sequential logic, and what would go wrong with the latter?
always @(clk) triggers on both the rising and falling edge of the clock, which does not match how a real flip-flop behaves (it only samples on one edge) — a design written that way would simulate one behavior but synthesize to hardware that behaves differently, since real flip-flops are edge-triggered on exactly one edge. always @(posedge clk) (or negedge) matches real hardware and is the only form that reliably synthesizes to a standard flip-flop.
Q6.How do you avoid accidentally inferring a latch in combinational logic?
An unintended latch gets inferred whenever a combinational always block (`always @(*)`) doesn't assign a value to an output on every possible path through the block — synthesis has to insert memory to 'hold' the last value for the unassigned case, even though the designer never intended any memory there. The fix is to make sure every output is assigned a default value at the very top of the block (or that every if/case statement has a complete, unconditional else/default branch covering all outputs) so the logic is provably combinational — fully determined by current inputs with no path where an output is left unspecified.
Q7.What's the danger of an inferred latch from an incomplete combinational always block (a missing else branch), and how do you catch it?
If a combinational always @(*) block doesn't assign a signal in every possible branch, synthesis has to infer a latch to hold the signal's previous value in the unassigned case — usually unintended, and a common source of simulation/synthesis mismatch and timing problems. It's caught by always fully specifying every branch (adding an else or a default case) and by lint tools that explicitly flag inferred-latch warnings.
Q8.What is the purpose of a default case (or default branch) in a `case` statement, even when you believe you've covered every possible value?
A default branch protects against unreachable-in-theory-but-possible-in-practice situations — a select signal might carry more bits than the number of enumerated cases actually needs (e.g. a 3-bit selector for a 6-state FSM has two unused encodings), and without a default, synthesis has no defined behavior for those unlisted values, which typically infers unwanted latches for every output in that case statement. Adding an explicit default that assigns known, safe values to every output closes that gap, keeps the block provably combinational, and gives the design deterministic (if arbitrary) recovery behavior if an illegal/unreachable state is ever somehow entered, such as due to a soft error flipping a state register bit.
Q9.What is the risk of using a `casex` statement versus `casez`, and why do some verification teams ban casex outright?
Both `casex` and `casez` allow wildcard bits in case-item comparisons, but `casex` treats X (unknown) and Z (high-impedance) bits in the case expression itself as don't-cares during matching, not just as literal wildcard characters in the case items. This means that if a signal happens to actually be X in simulation — often itself a sign of an uninitialized register or a real bug — `casex` can silently match it against a branch as if it were a valid, intentional wildcard, masking the underlying problem instead of flagging it. `casez` only treats explicit `?` characters in the case items as wildcards and does not treat X in the expression as a wildcard, making it much safer for exactly this reason, which is why many verification style guides prohibit `casex` in synthesizable RTL.
Q10.What's the difference between a Moore and a Mealy finite state machine?
In a Moore machine, the output depends only on the current state — outputs are registered and stable for the entire clock cycle once the state has settled, which tends to produce cleaner, more predictable timing at the cost of the output sometimes lagging an input by one extra cycle. In a Mealy machine, the output depends on both the current state and the current input, so it can react to an input within the same cycle it arrives (no extra latency), but the output is now a combinational function of an input, which can introduce timing/glitch considerations that a pure Moore machine avoids. Choosing between them is a real design tradeoff between reaction latency and output timing cleanliness.
Q11.Design an FSM for a simple vending machine (or similar control problem). How do you choose between binary and one-hot state encoding, and why might it matter for a Qualcomm-scale SoC control block?
Binary encoding uses the fewest flip-flops (log2 of the state count) but needs wider decode logic on every transition; one-hot encoding uses one flip-flop per state but keeps next-state logic simple and often faster, since each state's transition logic is independent. On a small control FSM the choice barely matters, but on a large, timing-critical control block, one-hot can win on speed at the cost of area — exactly the kind of area/timing tradeoff synthesis tools are told to make via encoding directives.
Q12.How would you write a parameterized N-bit up/down counter in synthesizable Verilog?
Declare a module with a `parameter N` controlling the width of the count register, an `output reg [N-1:0] count`, and a single clocked always block sensitive to the clock (and typically an asynchronous or synchronous reset). Inside, check reset first and clear the count; otherwise, based on a direction control signal, either increment or decrement the count using a non-blocking assignment. Making the width a parameter rather than hardcoding it lets the same verified RTL be instantiated at any bit width the design needs, which is standard practice for reusable building blocks.
Q13.Implement a mod-N counter with synchronous reset. What's the most common off-by-one bug candidates introduce?
The classic mistake is comparing against N instead of N-1 before wrapping back to zero, which makes the counter produce N+1 distinct states instead of N. Since counting starts at 0, the wrap condition has to be count == N-1, not count == N.
Q14.How do you correctly model a synchronous reset versus an asynchronous reset in RTL, and what's the tradeoff?
A synchronous reset is checked only on the active clock edge, inside the sensitivity list as just `always @(posedge clk)`, with the reset condition checked first inside the block — this guarantees reset only ever takes effect in alignment with the clock, simplifying static timing analysis since reset is just another synchronous input. An asynchronous reset is included directly in the sensitivity list, `always @(posedge clk or posedge rst)`, letting it force the flop into its reset state immediately regardless of the clock, which is useful for a true power-on/emergency reset that must work even if the clock isn't toggling yet — but it requires careful handling of reset release timing (synchronizing the de-assertion) to avoid a reset-recovery CDC-style hazard across the chip.
Q15.How would you structurally instantiate multiple identical sub-modules, such as building a ripple-carry adder from full adders?
Define the smaller module (a full adder, say) once with its own inputs and outputs, then instantiate it multiple times inside the parent module, connecting each instance's ports — either positionally or by name (`.port_name(signal)`, generally preferred for readability and to avoid connection-order bugs) — chaining any signals that need to pass from one instance to the next, such as a carry-out of one stage feeding the carry-in of the next. This structural style keeps the reusable logic defined exactly once, verified once, and then composed at whatever scale is needed rather than duplicating logic by hand.
Q16.Explain how you'd implement and verify a simple LFSR (linear feedback shift register) in Verilog.
An LFSR is a shift register where the input fed back into the newly-freed bit position is computed as the XOR of specific 'tap' bit positions rather than any external input — for example, a 4-bit LFSR might feed back `seq_out[3] ^ seq_out[2]` into the new LSB while shifting everything else left by one. The critical implementation detail is the seed: the register must never be reset or loaded with an all-zero value, because XOR-ing zeros together only ever produces more zeros, permanently locking the LFSR into a dead state with no pseudo-random sequence at all — so reset logic has to load a known non-zero seed (like 4'hF) rather than the more typical all-zero reset used elsewhere in a design.
Q17.Design a debounce circuit for a button/switch input. How do you handle the fact that a mechanical switch bounces for a few milliseconds before settling?
The standard approach samples the input on every clock edge and only accepts a new stable value once it has held constant for a fixed number of consecutive samples (e.g., long enough to exceed the switch's known bounce time) — typically implemented with a small counter that resets whenever the sampled input changes, and only updates the debounced output once the counter reaches its threshold.
Q18.What's the difference between synthesizable and non-synthesizable Verilog constructs? Give a couple of examples of each.
Synthesizable constructs describe hardware structure and behavior that a synthesis tool can map to real gates/flops: always blocks with proper edge sensitivity, case/if-else for combinational and sequential logic, module instantiation. Non-synthesizable constructs are simulation-only: explicit delays (#10), $display/$monitor for debug printing, initial blocks used for anything beyond testbench setup, and file I/O — these have no hardware equivalent and are stripped out or rejected by synthesis tools.
Q19.Given RTL with a subtle bug — say, a counter that never reaches its terminal count — how would you approach debugging it out loud in an interview?
Start from the specification, not the code: state what the terminal count should be and under what condition the counter should stop or wrap. Then trace the actual increment and comparison logic line by line against that condition, checking boundary values (off-by-one on the compare, wrong reset value, a stale variable used in the comparison) before assuming anything more exotic — most "the FSM/counter never reaches state X" bugs in an interview setting are a single wrong comparison operator or literal.
Practice Verilog coding exercises →Try the free Virtual Interview →