90+ free RTL questions on what interviewers actually ask: blocking vs non-blocking, latches, resets, FSMs, FIFOs, clock dividers, arbiters, timing, CDC and synthesis. Includes 18 worked answers with code, 8 solved problems, a buggy module reviewed line by line, a FIFO depth calculator and a rapid-fire round.
A questions-only bank from Verilog basics to debugging and real scenarios, built to make you answer and code, not just read.
RTL design interviews check one thing above all: when you write Verilog, do you see the hardware it becomes? Every follow-up comes from there. Why did that if make a latch? Which signal is the flop? What happens on the cycle after reset? What does this look like after synthesis, and will it meet timing?
Start with the formula sheet and the table of what synthesis builds, skim the 50 questions by topic, then work through the worked answers and solved problems with a simulator open. Finish with the code review: finding bugs in someone else's RTL is a favourite senior-round exercise.
Blocking vs non-blocking, wire vs reg, latches, sync vs async reset, MUXes, encoders, counters, Moore vs Mealy. Short questions, but every one has a "why" behind it.
An edge detector, a divide-by-3 clock, a sync FIFO, a sequence detector, a round-robin arbiter. They watch how you handle reset, corner cases and the cycle-by-cycle behaviour.
"Simulation passes but gates fail", "it misses 500 MHz", "data corrupts across clocks", "synthesis reports latches". They want a method: what you'd check, in what order, and why.
Sketch the registers, the logic between them and the clock. Code written from a block diagram is shorter, cleaner and easier to defend.
Clocked blocks with <= for registers, always @(*) or assign for logic. Mixing them is where most interview bugs start.
Sync or async, active high or low, which registers need it and how it's released. Interviewers notice when reset is an afterthought.
Full and empty at the same time, counter wrap-around, back-to-back requests, the first cycle after reset. Say them before you're asked.
What will this infer: flops, a latch, a priority chain, a RAM, a multiplier? How deep is the logic between registers?
One sentence on the test (the corner cases, an assertion, a waveform you'd check) turns a correct answer into a convincing one.
Design questions often hide a small calculation: a width, a depth, a frequency or a failure rate.
Worst-case burst from the writer, steady draining by the reader. Works for sync or async FIFOs.
"What does this infer?" is behind half of all RTL questions. Know the pattern, the result, and the trap.
| YOU WRITE | SYNTHESIS INFERS | WATCH OUT FOR |
|---|---|---|
always @(posedge clk) with <= | Flip-flops (one per bit assigned) | Blocking = here can merge or reorder registers |
always @(*), every output assigned on every path | Combinational logic | Reading a signal it also drives: a combinational loop |
always @(*) with a missing else or default | A latch | Timing that's hard to analyse, glitches, lint warnings |
A long if / else if chain | A priority encoder feeding a MUX | Depth grows with each branch; use case if branches are exclusive |
case on a select signal | A parallel MUX | Overlapping items still create priority |
A reg array with a clocked read | Block RAM (on FPGA) or a memory macro | A reset on the array or an async read prevents RAM inference |
a * b, a + b | Multiplier (or DSP block), adder | Result width: let a sum grow one bit, a product to the sum of widths |
A for loop | Replicated hardware, fully unrolled | It's not a loop in time; a 64-iteration loop is 64 copies |
initial, #5 delays | Nothing (ASIC); init values only on some FPGAs | Simulation that only works because of delays |
A sample of what RTL interviewers ask most. The 550-question ebook covers every topic in much more depth, with 100 coding problems.
Where RTL sits, and what it hands on.
The constructs that turn into circuits.
reg, and does that make it a register?parameter, localparam and `define?generate block, and when would you use one?The source of most simulation surprises.
= in a clocked block risk a race?assign and a procedural assignment?always @(*) preferred over a hand-written list?Flops, enables, counters and reset strategy.
MUXes, encoders and arithmetic.
States, encodings and coding styles.
The blocks you'll be asked to write.
What your RTL does to the critical path.
Data that moves between clocks.
From RTL to netlist, and back when it breaks.
11 chapters from Verilog basics to scenarios, including 100 write-the-RTL problems and 50 debugging and scenario questions. Questions only, so you practise producing answers.
Answer out loud (or in a simulator) first, then open each one. Every answer shows what a strong reply covers, what the interviewer is listening for, and the slip that costs the most.
Blocking (=) updates the variable immediately, so the next statement sees the new value. Non-blocking (<=) evaluates every right-hand side first and updates all left-hand sides together at the end of the time step, which is how flip-flops behave on a clock edge.
// Non-blocking: a real 2-stage shift register
always @(posedge clk) begin
q1 <= d;
q2 <= q1; // old q1
end
// Blocking: q2 gets d in the same cycle, one flop disappears
always @(posedge clk) begin
q1 = d;
q2 = q1; // new q1
endRule: <= in clocked blocks, = in combinational blocks, and never mix the two on the same variable.
always @(*)
if (sel) y = a; // no else: y must hold its valueWhen sel is 0, nothing assigns y, so it has to remember its last value. Remembering without a clock is a latch. Fix it by assigning the output on every path, most simply with a default at the top:
always @(*) begin
y = b; // default
if (sel) y = a;
endThe same applies to a case without a default and to any output assigned in only some branches.
The common answer: assert asynchronously, deassert synchronously, with a reset synchroniser per clock domain:
always @(posedge clk or negedge rst_n_in)
if (!rst_n_in) {r2, r1} <= 2'b00;
else {r2, r1} <= {r1, 1'b1};
assign rst_n = r2; // use this everywhere in the domainChoice: Moore (or a Mealy with registered outputs) when outputs leave the block or drive control signals; Mealy when a one-cycle response matters and the outputs stay inside a timed, synchronous path.
An if / else if chain has priority: the first true condition wins, so synthesis builds a chain of MUXes where the last branch passes through every stage. A case on one select signal with exclusive items describes a parallel MUX, which is usually faster and smaller.
// Priority: req[0] wins over req[1] over req[2]
always @(*)
if (req[0]) gnt = 3'b001;
else if (req[1]) gnt = 3'b010;
else if (req[2]) gnt = 3'b100;
else gnt = 3'b000;Use priority only when the specification has it. Avoid full_case and parallel_case pragmas: they change what synthesis builds without changing what simulation does.
An enable flop keeps the clock running and uses a MUX to feed back the old value. A gated clock stops the clock to the register, saving the clock and flop switching power that the enable flop still burns.
// Enable: clock always toggles
always @(posedge clk) if (en) q <= d;
// Never gate with a plain AND in RTL: glitches when en changes
// assign gclk = clk & en; // don't
// Use the library's integrated clock-gating (ICG) cell,
// or let synthesis insert it from the enable above.Clock gating pays off for wide registers (a bank of 32 or more bits) that are idle often. The ICG holds the enable in a latch while the clock is high, so the gated clock can't glitch.
Use pointers one bit wider than the address. Equal pointers mean empty; equal addresses with different MSBs mean the write pointer has lapped the read pointer, so the FIFO is full.
module sync_fifo #(parameter W = 8, AW = 4) (
input clk, rst_n, wr, rd,
input [W-1:0] din,
output [W-1:0] dout,
output full, empty);
reg [W-1:0] mem [0:(1<<AW)-1];
reg [AW:0] wp, rp; // one extra bit
assign empty = (wp == rp);
assign full = (wp[AW] != rp[AW]) && (wp[AW-1:0] == rp[AW-1:0]);
assign dout = mem[rp[AW-1:0]];
always @(posedge clk or negedge rst_n)
if (!rst_n) begin wp <= 0; rp <= 0; end
else begin
if (wr && !full) begin mem[wp[AW-1:0]] <= din; wp <= wp + 1; end
if (rd && !empty) rp <= rp + 1;
end
endmoduleThen say what you'd add: a registered dout for RAM inference, overflow and underflow flags, and a test with simultaneous read and write when full and when empty.
reg in_d;
always @(posedge clk or negedge rst_n)
if (!rst_n) in_d <= 1'b0;
else in_d <= in;
assign rise = in & ~in_d; // falling: ~in & in_d, any: in ^ in_dIf in comes from another clock domain or a pin, synchronise it with two flops first and detect the edge on the synchronised copy; otherwise the pulse can be lost or doubled. For a glitch-free registered output, register rise as well, at the cost of one cycle.
A mod-3 counter gives a 1/3 duty output. Make a copy of that output shifted by half an input period on the falling edge, and OR the two: the result is high for 1.5 of 3 input cycles.
reg [1:0] cnt;
reg p, n;
always @(posedge clk or negedge rst_n)
if (!rst_n) cnt <= 0;
else cnt <= (cnt == 2) ? 0 : cnt + 1;
always @(posedge clk or negedge rst_n)
if (!rst_n) p <= 0; else p <= (cnt == 0); // high 1 of 3
always @(negedge clk or negedge rst_n)
if (!rst_n) n <= 0; else n <= p; // shifted half a cycle
assign clk_div3 = p | n; // high 1.5 of 3Mention that in a real chip this output would come from a PLL or a library divider cell and be defined to STA as a generated clock; in most RTL, a clock enable that pulses every third cycle is the better choice.
Keep a pointer to the last winner. On each cycle, search for the first request after that pointer, wrapping around, and grant it. That way every requester gets a turn and nobody starves.
reg [1:0] last; // last granted index
integer i;
reg [1:0] idx;
always @(*) begin
gnt = 4'b0;
for (i = 1; i <= 4; i = i + 1) begin
idx = last + i; // wraps naturally in 2 bits
if (req[idx] && gnt == 4'b0) gnt[idx] = 1'b1;
end
end
always @(posedge clk or negedge rst_n)
if (!rst_n) last <= 2'd3; // so requester 0 goes first
else if (|gnt) last <= (gnt[1] ? 2'd1 : gnt[2] ? 2'd2 : gnt[3] ? 2'd3 : 2'd0);For many requesters, mention the faster masked-priority version: mask off requests at or below the last winner, take the lowest set bit with x & -x, and fall back to the unmasked request if the masked one is empty.
The cleanest style separates the state register from the next-state logic, with outputs either decoded from the state (Moore) or registered:
localparam IDLE = 2'd0, RUN = 2'd1, DONE = 2'd2;
reg [1:0] st, nx;
always @(posedge clk or negedge rst_n)
if (!rst_n) st <= IDLE; else st <= nx;
always @(*) begin
nx = st; // default: stay
case (st)
IDLE: if (start) nx = RUN;
RUN: if (last) nx = DONE;
DONE: nx = IDLE;
default: nx = IDLE; // recover from illegal states
endcase
endA two-flop synchroniser alone can miss it: the slow clock may never sample a pulse shorter than its own period. Convert the pulse to a level change first, synchronise the level, then detect its edge on the other side.
// fast domain: toggle on each pulse
always @(posedge clk_f or negedge rst_f_n)
if (!rst_f_n) t <= 1'b0; else if (pulse_f) t <= ~t;
// slow domain: synchronise, then edge-detect
always @(posedge clk_s or negedge rst_s_n)
if (!rst_s_n) {s3, s2, s1} <= 3'b0;
else {s3, s2, s1} <= {s2, s1, t};
assign pulse_s = s3 ^ s2;The limit: pulses must be spaced far enough apart (a few slow-clock periods) or toggles merge. If they can arrive back to back, use a handshake or an async FIFO.
full_case/parallel_case pragmas, initial blocks or # delays the design relied on.if (x) takes the else branch and hides unknowns; gates propagate X. Look for uninitialised registers without reset.Run lint and logic equivalence (RTL vs netlist) to separate a coding problem from a timing problem quickly.
Then say the cost: extra latency, area, or a protocol change for the blocks around yours.
Synchronising each bit separately doesn't work: bits resolve independently, so for one cycle the receiver can see some old bits and some new bits, a value that never existed.
Finish with how you'd prove it: a CDC tool run and an assertion that the bus is stable while the control bit crosses.
assigns or always blocks driving one net give X when they disagree.Lint warnings usually point straight at the cause, so read them before stepping through waves.
These come up in written tests and as quick checks during design questions. Solve each on paper, then tap to check.
T_min = 0.4 + 3.2 + 0.3 − 0.1 = 3.8 ns f_max = 1 / 3.8 ns ≈ 263 MHz
Note: the positive skew helped setup by 0.1 ns, but it also makes the hold check 0.1 ns harder.
Burst time = 120 / 200 MHz = 600 ns Read rate = 100 MHz / 2 = 50 M words/s Read in 600 ns = 600 ns × 50 M/s = 30 words Minimum depth = 120 − 30 = 90 words Async FIFO → round up to 128, pointers 8 bits
Note: add a few words for the synchroniser delay on the flags. The calculator above shows the same numbers.
2^5 = 32 < 60 ≤ 64 = 2^6 → 6 bits Counts 0 … 59; wrap to 0 when count == 59
Note: compare against 59, not 60. Comparing against 60 gives a mod-61 counter.
Binary → Gray: 1011 ^ 0101 = 1110
Gray → binary: b3 = 0
b2 = 1 ^ 0 = 1
b1 = 1 ^ 1 = 0
b0 = 0 ^ 0 = 0 → 0100 (4)Note: check by going forward again: 0100 ^ 0010 = 0110.
Denominator = 100 ps × 500 MHz × 50 MHz = 2.5 × 10^6 /s
One flop: t_r/τ = 0.4 / 0.02 = 20
MTBF = e^20 / 2.5e6 ≈ 4.85e8 / 2.5e6 ≈ 194 s
Two flops: t_r/τ = 2.2 / 0.02 = 110
MTBF = e^110 / 2.5e6 ≈ 2.4 × 10^41 sNote: a failure every three minutes becomes effectively never. That exponential is why the second flop is standard.
Before: T = 8 + 0.5 = 8.5 ns → 117.6 MHz, latency 8.5 ns
After: T = 2 + 0.5 = 2.5 ns → 400 MHz
throughput 400 M results/s (3.4×)
latency = 4 × 2.5 = 10 nsNote: throughput goes up 3.4×, not 4×, because each stage pays the flop overhead; latency gets slightly worse.
Binary: ⌈log2 12⌉ = 4 Gray: 4 One-hot: 12 Johnson: 12 / 2 = 6
Note: one-hot uses 3× the flops but each next-state equation looks at only a few bits, which is why FPGAs often default to it.
Half period = 0.5 s × 50 MHz = 25,000,000 cycles Count 0 … 24,999,999, then toggle and restart 2^24 = 16.8 M < 25 M ≤ 33.6 M = 2^25 → 25 bits
Note: for logic inside the chip, a one-cycle enable every 50,000,000 cycles is safer than using the toggled signal as a clock.
"Here's a colleague's RTL. What's wrong with it?" is a classic senior question. This module should add up len for each valid beat and report the total and a done pulse at the end of each packet. It has five problems.
// pkt_bytes.v — accumulate len per beat, report on last beat module pkt_bytes ( input clk, rst_n, valid, last, input [7:0] len, output reg [15:0] bytes, output reg done); reg [15:0] acc; always @(posedge clk or negedge rst_n) if (!rst_n) acc = 0; (1) else if (valid) begin acc = acc + len; (1) if (last) bytes = acc; (2) end // acc never cleared (3) always @(*) if (valid && last) done = 1'b1; (4) always @(posedge clk) if (last) bytes <= 0; (5) endmodule
acc on the same edge races with this one.bytes is written here, with a blocking assignment that sees the already-updated acc. Whether the last beat is included depends on statement order, not on intent.acc is never cleared at the end of a packet, so the second packet's total includes the first.done has no else in a combinational block: a latch that goes to 1 and never comes back.bytes is also driven from a second always block: multiple drivers. Synthesis will error out; simulation will pick a winner by chance.The multiple drivers on bytes (5): it's a hard synthesis error and a sign the author doesn't think of each register as one flop with one source. Then the latch (4), which is a functional bug every cycle after the first packet, then the blocking assignments.
One clocked block owns every register, using <=, and the total includes the last beat explicitly:
always @(posedge clk or negedge rst_n)
if (!rst_n) begin acc <= 0; bytes <= 0; done <= 0; end
else begin
done <= valid && last;
if (valid) begin
if (last) begin bytes <= acc + len; acc <= 0; end
else acc <= acc + len;
end
endOnly if packets are at most 65,535 bytes: 258 beats of 255 bytes already overflow it. Ask for the maximum packet length, size acc from it with a parameter, or add an overflow flag.
A testbench with back-to-back packets, a one-beat packet (valid and last together), gaps in valid, and reset in the middle of a packet; plus an assertion that done is high for exactly one cycle per last beat. The ebook has full chapters of coding, debugging and scenario questions like this.
Screening calls often open like this. Answer each in a few seconds, then tap to check.
Non-blocking (<=).
always @(*), or always_comb in SystemVerilog.
A latch.
8.
3.
0100.
7.
No. Some FPGAs use it for power-up values.
Ignores it.
The current state only.
The current state and the inputs.
Asynchronous.
Synchronise the deassertion with a reset synchroniser.
A two-flop synchroniser.
Only one bit changes per increment.
No. Hold doesn't depend on the period.
Values including X and Z. Simulation only.
Replicated hardware, fully unrolled.
A net, such as a wire.
To tell full from empty.
Loops that "run", variables reused for different things, sequential thinking inside one cycle. Every line should map to something you could draw.
Blocking in a clocked block, or both styles on one signal. It may simulate, but it won't behave or synthesise the way you think.
One missing branch is a latch. Assign every output at the top of the block, every time.
Code that only works after a lucky first cycle. Say which registers reset, to what, and how reset is released.
Read and write together when full, a counter at its last value, two requests on the same cycle. Interviewers wait for you to bring them up.
A counter output or an AND gate used as a clock creates skew, glitches and timing nobody constrained. Use clock enables.
Whether you can write correct, synthesisable Verilog and explain the hardware it becomes: assignments and latches, resets, FSMs, FIFOs and other building blocks, then timing, clock domain crossing, synthesis and debugging for experienced candidates.
Most RTL rounds accept either. Verilog is the baseline; knowing the SystemVerilog design features (logic, always_ff, always_comb, enums, interfaces) is a plus. Verification features like classes and randomisation belong to DV rounds.
Almost always some. Expect one or two small blocks (a counter, an edge detector, an FSM, a FIFO) on a whiteboard or shared editor, usually followed by questions about corner cases and what synthesis builds.
No. A free simulator such as Icarus Verilog or Verilator with a waveform viewer is enough to write, test and debug every block on this page. A board helps if you're targeting FPGA roles.
Blocking vs non-blocking, latches, flip-flops and resets, MUXes and encoders, counters and shift registers, Moore and Mealy FSMs, then a sync FIFO and setup and hold. After that: CDC basics and synthesis.
RTL designers write the hardware description and own its correctness, timing and area. Verification engineers build testbenches to prove it works. Each interview touches the other's basics, but design rounds go deeper on synthesisable coding and microarchitecture.
Book a 1:1 mock interview with a mentor working in this exact domain.
Book a mock interview