Interviews
Combinational RTL Interview Questions
Practise the question, then the follow-ups an interviewer asks next.
Start with a Question TreeAI Mock Interview
- 8
- Question trees
- 39
- Follow-ups
- 60
- Wrong answers explained
- 18
- Runnable on a simulator
idx is the number of the highest set bit of req, and valid says any bit is set.
WhyWhy Casex Matches an Unknown InputCompareCase Inside vs CasezWhat IfA Priority Encoder for Any WidthScale ItCutting the Depth of a Wide Priority Encoder
+ 1 more follow-ups · Open this tree
Flagship Questions · With follow-ups
Question Trees
Each question opens into the follow-ups an interviewer asks next, the wrong answers people give, and what a fresher's and a senior's answer sound like.
-
A Priority Encoder with Wildcard Case Items
Write a 4-bit priority encoder.idxis the number of the highest set bit ofreq, andvalidsays any bit is set. 5 follow-ups · 8 pitfalls · 5 runnable- Asked at Micron
-
Unique Case vs Priority Case
What dounique caseandpriority casepromise, who checks the promise, and what can go wrong? 5 follow-ups · 7 pitfalls · 1 runnable -
Is the If Chain Slower Than a Case?
A reviewer says: "Rewrite this as acaseto get rid of the priority chain." Is the reviewer right? 5 follow-ups · 8 pitfalls · 3 runnable -
A Gate That Feeds Itself
What does this circuit do whenenis 0, and whenenis 1? 6 follow-ups · 7 pitfalls · 2 runnable -
The Link Where Each Side Waits for the Other
A source and a sink pass all their unit tests. Joined, the link never moves a beat. Here is the log. What is wrong? 5 follow-ups · 8 pitfalls · 1 runnable -
The Mux with a Short Sensitivity List
What does simulation show whenselchanges here? What does synthesis build? 4 follow-ups · 7 pitfalls · 3 runnable -
X Optimism in an If Statement
The select of anifis X in RTL simulation. What does the output show, and why can that hide a bug? 4 follow-ups · 7 pitfalls · 2 runnable -
RTL Passes, Gate-Level Simulation Fails
All 412 RTL tests pass. Zero-delay gate-level simulation fails three. Here is the summary. Where do you look first? 5 follow-ups · 8 pitfalls · 1 runnable- Likely at Arm
No question matches these filters.
Quick revision · One flat list
Question Bank
All 41 Combinational RTL questions, main and follow-up. Open a track, then a topic. Answer out loud first, then open the short answer.
Jump to a Track
41 questions · 8 topicsCombinational RTL
A Priority Encoder with Wildcard Case Items5
Write a 4-bit priority encoder. idx is the number of the highest set bit of req, and valid says any bit is set. L1L2
Give idx and valid defaults first, so there is no latch. Then list wildcard items from the top bit down: 4'b1???, 4'b01??, 4'b001?, 4'b0001. Items are checked in order, so the first match gives the highest set bit. Use case inside, or casez if your tools lack it, and never casex. Test all 16 values.
Why does casex match x001 against 4'b1???? And what does this casez version print for a floating top bit? L2
Wildcard case statements mask bits on both sides of the comparison. casex ignores X and Z wherever they appear, so the X in req matches the 1. casez does the same for Z, so z001 matches 1??? and this prints idx=3. In silicon the floating line is a real 0 or 1, so the gates may do something else.
Compare case inside with casez for the same encoder. L2
case inside compares with the wildcard equality operator, so only X, Z or ? bits in the items are masked. An unknown bit in req then fails every item and falls to default. It synthesizes to the same 5 cells as the casez version on this flow. Icarus 12 does not parse case inside, so check your tools first.
What if the request bus is 16 bits, or a parameter? Write the encoder without listing every item. L2
Use a for loop that walks from bit 0 up and overwrites idx on every set bit. The last write wins, so the highest set bit wins. Set the defaults before the loop. Synthesis unrolls the loop into a chain. On this flow the 16-bit version is 40 cells and 12 levels, so depth grows with width.
The 16-bit encoder is too deep for your clock. Restructure it. L2L3
Split the bus into four groups of four. Each group has a small encoder and a "busy" bit. A top encoder picks the highest busy group, and its number forms the upper bits of idx. The run proves the two versions equal on all inputs. On this flow the grouped one has 8 levels instead of 12, for about the same cells.
Unique Case vs Priority Case5
What do unique case and priority case promise, who checks the promise, and what can go wrong? L2L3
Both promise that some arm matches when there is no default. unique also promises at most one arm matches, so synthesis may ignore the order. priority keeps the order, and the first match wins. The simulator checks the promise each time the statement runs, while synthesis simply assumes it. If the promise is false, RTL and gates can disagree.
Why do the gates and the RTL disagree when two requests arrive at a unique selector? L2
The RTL runs the first matching arm, so it passes one data word. The netlist was built on the promise of one request at a time. Each data word is ANDed with its request and the results are ORed. With two requests, two words mix: 0010 | 0100 = 0110. The false promise became a functional bug.
Prove it: how do you catch that false promise in RTL, before the netlist shows it? L2
Run RTL simulation with the unique checks on. The simulator reports a violation when two items match, or when none does. Also assert $onehot0(req) at the block boundary, so the caller's side is checked. Formal can then prove that assertion from the requesters' logic.
Compare unique, unique0 and priority. When would you choose each? L2
priority keeps the order and only promises that something matches. Use it when overlaps are legal and the spec says who wins. unique promises exactly one match without a default. unique0 promises at most one and allows no match. Use unique0 for a decode where "nothing selected" is normal.
As a design lead, when do you allow unique, and how do you make sure the promise holds? L3L4
Allow it only where the inputs are exclusive by construction, such as a one-hot state register or a decoded address. Back each use with a one-hot assertion and a formal proof or a test that tries the overlap. Run regressions on a simulator that checks unique. Treat every violation as a design bug, not a warning.
Is the If Chain Slower Than a Case?6
A reviewer says: "Rewrite this as a case to get rid of the priority chain." Is the reviewer right? L1L2
Not here. Every condition compares the same 2-bit sel with a different constant, so at most one can be true. Synthesis sees that and builds a plain 4:1 mux. On this flow the chain is 27 cells and 4 levels, the same depth as the case. Priority is real only when conditions can be true together.
Compare the case version of the mux with the if chain. Which is smaller on this flow? L1L2
The case version has the same 4 levels but 60 cells, against 27 for the chain. The tool built it as decoders plus an AND-OR, not as a mux tree. Another synthesizer may give other numbers. The lesson is to measure your own flow, not to trust a style rule.
Then when does writing conditions in order really cost depth? L2
When the conditions test different signals that can be true together, such as separate request bits. Then the order is part of the function, because it decides who wins. Synthesis must keep it, and each extra condition adds a stage. That is the selector in the twin: 5 levels, not 3.
Follow-upPractise When the Order of Conditions Costs Depth →What if someone adds // synopsys parallel_case instead of unique? L2L3
The pragma is a comment that only synthesis reads. It makes the same promise as unique: the items never overlap. But the simulator ignores it, so nothing checks the promise. If it is false, RTL and gates disagree with no warning. Use unique, which makes the same promise and gets checked.
Compare full_case with parallel_case. What trap does full_case add? L2L3
full_case tells synthesis that unlisted values never happen, so it builds no latch for them. Simulation ignores the comment and still holds the old value for those values, as a latch would. So the RTL holds a value that the gates do not. A default assignment, or unique, makes both agree.
Scale it up: a 64-input, 32-bit mux sits on a critical path. How do you think about it? L2L3
A binary select feeds a tree of 2:1 muxes, about six deep for 64 inputs. If the select is already one-hot, an AND-OR structure avoids the decode. If the path is still too long, register the select early or split the mux across a pipeline stage. Lesson R5.5 measures these choices.
Follow-upPractise Very Wide Muxes →A Gate That Feeds Itself5
What does this circuit do when en is 0, and when en is 1? L0L1
With en at 0, y is simply 1. With en at 1, y = ~y, which has no stable value. Real gates oscillate at a rate set by their delay, like a ring oscillator. A zero-delay simulator keeps re-evaluating in the same time step, so it hangs or hits an iteration limit. The synthesis check reports a combinational loop.
Why can't static timing analysis handle a combinational loop? L1L2
STA adds delays along paths from a start point, such as a flop or an input, to an end point. Around a loop, a path comes back to where it started, so the arrival time has no limit. STA tools report loops for that reason. A tool that cuts a loop to continue may hide a real path.
Follow-upPractise Why Timing Analysis Cannot Time a Loop →Break it: the loop passes through two muxes whose selects are never set to close it at the same time. Is that safe? L2L3
Functionally it may never cycle. But the structural checks and STA see wires, not select values, so they still report a loop. Every team must then waive it and re-check it after each change. Recode it so no structural loop exists, for example by sharing one mux.
Follow-upPractise A Loop That Is Never Active →Compare an accidental combinational loop with a latch. A latch is also feedback, isn't it? L1L2
A latch is feedback with two stable states and a defined way to open and close it. In silicon it is one library cell, so timing tools know its checks. An accidental loop is built from ordinary gates. Its feedback may have no stable state, or two that nothing controls. Write a latch on purpose with always_latch so tools map it to a latch cell.
What if each module is clean on its own, and the loop appears only when they are connected? L2
That is the common case. Each module has a legal input-to-output path, and the two paths close a loop at the top. Per-module lint cannot see it. Run lint and the synthesis check on the connected design. Lesson R3.3 measured both tools finding such a loop through two modules.
Follow-upPractise A Loop That Runs Through Two Modules →The Link Where Each Side Waits for the Other5
A source and a sink pass all their unit tests. Joined, the link never moves a beat. Here is the log. What is wrong? L2L3
The source drives valid = have_data && ready, and the sink drives ready = valid && !full. Each waits for the other through logic, so the two ANDs form a combinational loop. Both 0 and both 1 are stable, and nothing picks one, so simulation shows X. The rule is that valid must not depend on ready. The sink may wait for valid.
Why does the handshake rule say valid must not wait for ready, but ready may wait for valid? L1L2
One side must be allowed to go first without waiting, or a deadlock is possible. The rule picks the sender. A sender that has data raises valid on its own. Then any receiver works, whether it waits for valid or raises ready early.
What if the sink raises ready before there is any valid? L1
That is allowed and often faster, because a beat moves in the first cycle valid rises. The sink must then be able to take a beat in any cycle where ready is high. Nothing transfers until both are high at the same edge.
Compare a ready that comes from a flop with one computed from logic. L2L3
A combinational ready can depend on what happens downstream in the same cycle. In a chain of stages it becomes one long path that grows with every stage. A registered ready cuts that path and every loop through it. But it arrives a cycle late, so the stage needs a spare entry, a skid buffer, to keep full rate.
Scale it: dozens of blocks from several teams meet on valid/ready ports. How do you stop this class of bug? L3L4
Write it down as an interface rule. No port has a combinational path from an input to an output, and valid never waits for ready. Check it on the joined design at every integration drop. Use lint and the synthesis check. Bind assertions on every port, such as valid staying high until the transfer.
The Mux with a Short Sensitivity List5
What does simulation show when sel changes here? What does synthesis build? L1L2
Simulation re-runs the block only when a or b changes. So when only sel changes, y stays stale. Synthesis builds logic from the expression, not from the list, so it gives a plain 2:1 mux. The RTL simulation now shows behaviour the chip does not have. Use always_comb, which wakes on every signal the block reads.
Why does synthesis build a full mux when the list leaves sel out? L1
Gates have no event list. A gate output follows its inputs all the time. Synthesis reads the assignments to learn the function, and the function of sel ? b : a is a mux. A sensitivity list only tells a simulator when to re-run the code.
What if the list has an extra signal that the block never reads? L1
The block runs more often than needed, which costs a little simulation time. Each run computes the same value, so the output never goes stale. There is no mismatch with the gates. The real danger is always a missing signal, never an extra one.
Follow-upPractise An Extra Signal in the Sensitivity List →Compare always @* with always_comb. Here the block calls a function that reads shift_en. What does Icarus print? L1L2
Icarus prints y=3 and the FAIL line. always @* waits only on what the block names, including function arguments, and shift_en is read inside the function. always_comb also waits on signals read inside called functions, so it gives 6. It also runs once at time zero, and it forbids other writers.
You own a legacy Verilog-2001 IP with hundreds of hand-written sensitivity lists. How do you remove this class of bug safely? L2L3
Run lint to list every incomplete sensitivity list. Convert combinational blocks to always_comb, which also checks for latches and second writers. Re-run the full regression, because a fixed list can change what the old tests saw. Then run an equivalence check of the old netlist against the new one, which should match.
X Optimism in an If Statement5
The select of an if is X in RTL simulation. What does the output show, and why can that hide a bug? L1L2L3
An if whose condition is X takes the else branch. So the output shows a clean value from the else side, and the unknown select disappears. In silicon the select is a real 0 or 1, and nobody knows which. RTL simulation looks fine while the chip may pick either branch. The ternary operator is less optimistic: it gives X wherever the two inputs differ.
Why would a language choose to hide X at all? L2
Because pessimism breaks real circuits in simulation. Take a synchronous reset that ANDs the data with an active-low reset. If an X data input made the AND output X, the flop would never reset in simulation. Silicon resets fine, because 0 AND anything is 0. Optimism matches silicon in cases like that and hides bugs in others.
Follow-upPractise Why the Language Is X-Optimistic →At the block level, how do you make sure X optimism is not hiding a reset or initialization bug? L3L4
Reset every control flop, and allow reset-less flops only in the datapath where a valid bit guards them. Add assertions that control signals are never unknown after reset. Run a formal X-propagation check, or gate-level simulation on the boot and reset tests. Some simulators also offer X-propagation modes that make RTL less optimistic.
Follow-upPractise An X-Propagation Strategy for a Block →Where does an X come from in RTL simulation in the first place? L1L2
The usual sources are flops with no reset and memories never written. Others are two drivers fighting on a net, an index out of range, and a missing input connection. An X on a control signal is the dangerous kind, because optimism can hide it. An X in a data word guarded by a valid bit is often harmless.
Follow-upPractise Where X Values Come From →Compare X optimism and X pessimism. Which one hurts you at gate level? L2L3
Optimism turns an unknown into a known value, so RTL looks better than silicon. Pessimism keeps X where silicon would have a real value, so simulation looks worse than silicon. Gate-level models are often pessimistic. An X that silicon would resolve can then spread and never clear, and the test fails for no real reason.
Follow-upPractise X Optimism vs X Pessimism →RTL Passes, Gate-Level Simulation Fails5
All 412 RTL tests pass. Zero-delay gate-level simulation fails three. Here is the summary. Where do you look first? L1L2L3
Zero delay rules out timing, so these are mismatches between RTL and netlist. A wrong value where RTL was right points to a sensitivity problem, such as always @* with a function. An X in gates where RTL showed 0 points to a missing reset. Either an inline initializer acted as the reset, or X optimism hid an unreset flop. Fix the RTL, not the netlist.
A colleague reruns the always @* test on Verilator and it passes. Why might one simulator hide the mismatch? L2
Verilator does not follow every event rule of the standard. On this example it recomputed y as plain logic, so it printed 6. Icarus follows the @* rule and keeps 3. A design whose result depends on the simulator is depending on code the gates do not have. Fix the code.
Compare lint with gate-level simulation as ways to catch simulation-synthesis mismatches. L2L3
Lint reads the RTL and flags risky constructs, such as short sensitivity lists, inline initializers and blocking writes in clocked blocks. It runs in seconds and needs no test. Gate-level simulation finds a mismatch only when a test exercises it, and it runs slowly. Use lint as the gate on every commit, and a small gate-level set as the net.
Follow-upPractise Lint vs Gate-Level Simulation for Mismatches →How can a design pass both RTL and zero-delay gate-level simulation and still fail in silicon? L3
Zero-delay gates still have no setup or hold time, so metastability and clock crossings look perfect. Real timing faults need static timing analysis, not simulation. Power-up values and analog effects are absent too. Gate simulation catches netlist mismatches, not the physics.
Follow-upPractise What Zero-Delay Gate Simulation Misses →What if the RTL also contains $display calls and checks? How do they behave in synthesis? L1L2
Synthesis cannot build a print or a check as logic. Tools ignore such code, reject it, or keep it as a cell with no output. Wrap simulation-only code in translate_off and translate_on pragmas, or in an ifndef SYNTHESIS guard. Then synthesis sees exactly the design.
Your Review Queue
Saved review reminders are coming soon.