Interviews
Latches and Scheduling Interview Questions
Practise the question, then the follow-ups an interviewer asks next.
Start with a Question TreeAI Mock Interview
- 5
- Question trees
- 25
- Follow-ups
- 38
- Wrong answers explained
- 11
- Runnable on a simulator
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.
-
The Shift Register Written with Blocking Assignments
This block is meant to be a three-stage shift register. What hardware does it really describe? 5 follow-ups · 7 pitfalls · 3 runnable -
Two Simulators, Two Answers
The same RTL givesb=0on Icarus andb=1on Verilator. The code is in the depth panel. Which simulator has the bug? 4 follow-ups · 7 pitfalls · 1 runnable -
A Blocking Temporary Inside Always_ff
This block mixes=and<=. Is it legal? How many flops does it build? 5 follow-ups · 8 pitfalls · 3 runnable -
Why Synthesis Builds a Latch
What makes synthesis infer a latch from combinational code, and how do you stop it? 6 follow-ups · 9 pitfalls · 2 runnable -
A Case Statement, a Default Arm and a Latch
This block has adefaultarm. Does it infer a latch? If so, on which signal? 5 follow-ups · 7 pitfalls · 2 runnable
No question matches these filters.
Quick revision · One flat list
Question Bank
All 27 Latches and Scheduling questions, main and follow-up. Open a track, then a topic. Answer out loud first, then open the short answer.
Jump to a Track
27 questions · 5 topicsLatches and Scheduling
The Shift Register Written with Blocking Assignments5
This block is meant to be a three-stage shift register. What hardware does it really describe? L0L1
It describes one flop, not three. A blocking assignment updates its variable at once, so s2 reads the new s1 and q reads the new s2. In one edge, d reaches q, so s1 and s2 are just wires. Write the three lines with <=. Then each right-hand side reads the value from before the edge, and you get three flops.
Why do nonblocking assignments model flip-flops correctly? L1
Real flops on one clock all sample their inputs at the same edge, before any of them changes. A nonblocking assignment copies that. Its right-hand side is read when the statement runs, and the update happens later, in the NBA region. So every flop sees the values from before the edge, whatever order the code runs in.
Follow-upPractise Why Nonblocking Assignments Model Flops →What if you swap two registers at a clock edge, once with = and once with <=? What does this print? L0L1
With =, a becomes 2 first, then b copies the new a, so both are 2. With <=, both right-hand sides read the old values, so c and d swap to 2 and 1. No temporary variable is needed. This is the shortest proof of the old-value rule.
Writing q = s2; s2 = s1; s1 = d; also gives three flops. Compare it with the nonblocking version. Why not use it? L1L2
Both give three flops in this block. The reversed version only works because of the line order, so a later edit can break it silently. The blocking writes also update at once, so another block reading s1 at the same edge has a race. With <= the order does not matter and readers elsewhere see the old value.
How do you break this further: what happens if two always_ff blocks both write q? L2
A flop has one D input, so two writers describe hardware that cannot exist. In simulation, the last write in a time step wins, which depends on block order. always_ff makes this a compile error, because a variable it writes may not be written anywhere else. Plain always @(posedge clk) gives no such check.
Two Simulators, Two Answers5
The same RTL gives b=0 on Icarus and b=1 on Verilator. The code is in the depth panel. Which simulator has the bug? L1L2
Neither simulator has a bug. The design has a race. Both blocks wake at the same edge, and the standard lets a simulator run them in any order. Because a is written with =, the reader sees the old or the new value depending on that order. Write a with <=, so every reader at that edge sees the old value in any tool.
Why does changing a = a + 1 to a <= a + 1 remove the race, even though the block order is still open? L1L2
With <=, the write to a waits for the NBA region. Every block that wakes at the edge runs before that, in the Active region. So the reader always sees the old a, whichever block runs first. The order still varies, but it no longer changes the result.
You inherit 200 RTL files written over ten years. How do you find and stop races? L3
Start with lint: report blocking writes in clocked blocks and variables written by more than one process. Convert clocked blocks to always_ff, so the compiler rejects a second writer. Run regressions on two simulators and treat any difference as a bug. Then make the lint rules a commit gate, so new code stays clean.
Compare lint and running a second simulator as ways to find races like this one. L2
Lint reads the code and flags the pattern, such as a blocking write in a clocked block. It works even if no test shows a wrong value. A second simulator finds only races that change a result in the tests you run. Lint is cheaper and more complete for coding races. Two simulators also catch other order-dependent behaviour.
Follow-upPractise Lint vs a Second Simulator for Finding Races →What if the race is between the testbench and the design? The bench writes din = 1 at posedge clk, and a flop samples din there. L1L2
It is the same race. The bench write and the flop read both run in the Active region. So the flop may capture the old or the new din. Drive inputs away from the sampling edge, for example on the falling edge or through a clocking block. Then the design always samples a settled value.
A Blocking Temporary Inside Always_ff6
This block mixes = and <=. Is it legal? How many flops does it build? L1L2
It is legal, and it builds 8 flops, all for avg. sum is written with = before it is read, so the next line sees the new sum. It needs no memory, so synthesis makes it a wire from the adder. Keep sum local to the block. Another block that reads it at the same edge would race.
If blocking writes cause so many bugs in clocked blocks, why does the language allow them in always_ff? L2
Some clocked blocks need an intermediate value inside the same edge, such as a temporary or a loop variable. A blocking write gives that value at once. The rules for always_ff limit timing controls and other writers. They do not ban =, so lint is where the house rule gets enforced.
What if a second always_ff block reads sum at the same clock edge? L2
Then the two blocks race. sum changes in the Active region at the edge, and the reader runs in the same region in either order. It may get this edge's sum or the last one. Declare the temporary inside the block, so nothing outside can read it.
What if Icarus and Verilator give the same result for that race? Is the code safe then? L2L3
No. The standard lets the two blocks run in either order inside the Active region. Two tools agreeing only means they chose the same order today. A new version, an extra block or a different file order can change it. Lint for a blocking variable read outside its block, and declare the temporary locally.
Follow-upPractise When Two Simulators Agree on a Race →Compare the inline temporary with computing sum in its own always_comb block. L2
Both give the same hardware. A separate always_comb keeps each block to one kind of assignment, so lint rules stay simple. It also makes sum visible in waveforms. The inline form keeps the logic in one place but mixes = and <=. Many style guides choose the separate block for that reason.
How would you break the temporary? Suppose sum is written only under if (en). What does this print, and what does synthesis build? L2L3
When en is 0, sum keeps the value from the last enabled edge, so it is memory now. The run prints avg=15, from the old inputs. On this flow Yosys did not add 9 flops for sum. It folded the memory into an enable on the 8 avg flops. Another tool may build it differently, so read the report.
Why Synthesis Builds a Latch6
What makes synthesis infer a latch from combinational code, and how do you stop it? L0L1L2
A combinational block must give every output a value on every path. If one path assigns nothing, the output must keep its old value. Keeping a value needs memory, so synthesis builds a level-sensitive latch. Fix it by assigning every output on every path: a default at the top, an else, or a default arm. Then write the block as always_comb, so lint and synthesis report any path you missed.
Why is an unintended latch a real problem and not just a lint warning? L1L2
The latch is memory the spec never asked for, so the output can show a stale value. While its enable is active the latch is transparent, so glitches on its input pass straight through. Static timing and test tools treat latches specially, so many teams avoid them to keep those steps simple. The design may work in RTL simulation and still fail in the lab.
Follow-upPractise Why an Unwanted Latch Is a Problem →Compare a latch and a flip-flop fed the same data. What does this print? L1L2
The latch follows d while the clock is high, so it shows 1 at t=13 and 0 at t=18. The flop changes only at rising edges. It took 0 at t=10 and holds it until t=30, when it takes 1. When the clock is low, the latch holds. Designers use latches on purpose in clock gating cells and in some latch-based pipelines.
What if the decoder is written with always_comb instead of always @*? Is the latch gone? L1L2
No. Simulation behaves the same, and the output still holds its value when en is low. The difference is in the tools that read the intent. Lint names the latch, and many synthesizers warn or stop. On this site's flow, synthesis refuses the block with a logic-loop error. Either way, you still have to add the missing assignment yourself.
How can the "assign a default at the top" fix itself hide a functional bug? L2
The default removes the latch, but it also gives a value to any arm you forgot. A missing arm then outputs the default quietly, often zero, and no tool complains. Choose a default that the spec allows, and make the testbench cover every input value. Some teams use unique case, so that simulation reports a value no arm matches.
You lead an RTL team. What rules and checks keep unintended latches out of every block? L3
Make always_comb mandatory for combinational blocks and always_latch for the rare intended latch. Run lint on every commit, with a latch inference message as an error. Make the synthesis latch count a gate in the flow, with a reviewed waiver list for intended latches. Review default values against the spec, because removing a latch can hide a missing case.
A Case Statement, a Default Arm and a Latch5
This block has a default arm. Does it infer a latch? If so, on which signal? L1L2
Yes, one latch, on valid. The default arm covers values of op that no other arm lists. It does not cover outputs that a listed arm forgets. In the op == 2 arm nothing assigns valid, so it keeps its last value. res is assigned in every arm, so it has no latch.
Why does a default arm not prevent a latch on an output that one arm forgets? L1
Latch inference is decided per variable, per path. The default arm adds one path: the case values no other arm lists. Each listed arm is still its own path. If a listed arm skips an output, that path still holds the output.
Follow-upPractise What a Default Arm Covers →Compare fixing the block with defaults at the top against adding unique to the case. L2
Defaults at the top give every output a value on every path, so the latch goes away. unique only makes promises about which arms match. It does not assign valid in the op == 2 arm, so the latch stays. Use the defaults to remove the latch, and add unique only if the arms truly cannot overlap.
What if someone fixes it by assigning valid = 'x in the default and at the top? L2L3
The latch goes away, because every path now assigns something. Synthesis reads X as "don't care" and may pick any value that gives smaller logic. Simulation shows X, which helps catch a path that should never run. The guideline today is unique or priority instead, because they give the same freedom with built-in checks.
Break it: show how the 'x default can hide a real bug in simulation. L2L3
Downstream logic such as if (valid) treats X as false and takes the else branch. So the path that should never run looks harmless in simulation. Synthesis may choose 1 for that X, and the gates then assert valid there. An assertion that valid is never unknown catches it at the source.
Your Review Queue
Saved review reminders are coming soon.