Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Digital Design & Verification (Verilog, SystemVerilog, VLSI)  Module 15 – Interview preparation for design and verification roles

RTL design interview problems, solved and tested

Solve RTL whiteboard problems the way interviews test them: a 50 % odd clock divider and a one-hot check, with short testbenches, synthesis counts and lint.

  • Intermediate
  • 20 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Solve the odd clock divider and the one-hot check at a whiteboard, and explain the trade-offs of each
  • Explain a design's flip-flops, longest path and first cycle after reset aloud, with synthesis numbers
  • Verify a whiteboard design with a self-checking testbench of 20 to 30 lines, at more than one parameter value
  • Avoid the coding slips interviewers listen for, and find them with lint before anyone else does

Before you start

What you’ll learn · Before you start

What you will learn

  • Solve the odd clock divider and the one-hot check at a whiteboard, and explain the trade-offs of each
  • Explain a design's flip-flops, longest path and first cycle after reset aloud, with synthesis numbers
  • Verify a whiteboard design with a self-checking testbench of 20 to 30 lines, at more than one parameter value
  • Avoid the coding slips interviewers listen for, and find them with lint before anyone else does

Before you start

On this page

A counter that wraps every three rising edges divides a clock by 3, but its output is high for one cycle in three, a 33 % duty cycle. Asked for 50 %, the answer needs one more flip-flop, on the falling edge, and a strong answer then says why that works, what it costs and how to prove it in the minutes that are left.

This lesson works two whiteboard problems in full, an odd clock divider and a one-hot check, in the order a strong answer takes: clarify, sketch, write, explain, verify. Each design ran on Icarus Verilog 13.0 and Verilator 5.052 under a testbench of 20 to 30 lines, Yosys 0.69 counted what each one costs, and Verilator’s lint caught four slips in a third file. The divider’s model runs in your browser.

A strong answer has five steps, and the RTL is only the third

An RTL whiteboard problem is a specification of one or two lines and a whiteboard or a shared editor to answer it on. The code is the part you have practised most, and it is one step of five. Step through an answer to the first problem of this lesson, divide a clock by 3 with a 50 % duty cycle: The share of each period that a clock or a pulse train is high: 50 % for a symmetric clock, 33.3 % for a divide-by-3 counter output that is high for one cycle in three. More in “Clocks for RTL designers: enables, gating, muxes”:

Interactive · stage by stage Five steps of a whiteboard answer, on the divide-by-3 Step through one answer to the lesson's first problem. Each stage is a step an interviewer listens for, and the last can send you back to the first.
Five steps of a whiteboard answer, on the divide-by-3, step 8 of 8. Showing Clarify the spec, Draw the block diagram, Write the RTL, Timing, area, reset, State how you verify.a follow-up changes itClarify the specduty, reset, N, targetDraw the block diagramregisters and the logicWrite the RTLone block per registerTiming, area, resetsaid aloud, with numbersState how you verifythe checks and a slip

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. The prompt. The prompt is one line: divide a clock by 3 with a 50 % duty cycle. Nothing is drawn or written yet, and the first minutes decide whether the rest answers the question that was asked.
  2. Clarify. Ask before you design: is the input clock itself 50 %? Must any odd N work, or only 3? Is the reset asynchronous? Is this for a chip or an FPGA? Each answer changes the design, and asking shows the interviewer how you work.
  3. Draw. Sketch the registers first: a 2-bit counter and pos_q on the rising edge, neg_q on the falling edge, and an OR gate in front of the output. The plan is checked here, while it costs nothing to change.
  4. Write. Write one always_ff block per clock edge, with the reset in each, and keep the output to the OR of two flip-flops. Name the signals as you drew them, so the interviewer can follow from the sketch.
  5. Explain. Say what synthesis builds: four flip-flops and three gates when N is 3, one flip-flop on the falling edge. Say what happens at reset: the counter starts at N - 1, so the first pulse is whole. The output is a clock, so timing analysis must be told it is one.
  6. Verify. Name the checks and what they catch: every period 30 ns and every high time 15 ns, for N of 3, 5 and 7, and one slip they would find, the counter reset to 0, which cuts the first pulse when N is 5.
  7. The follow-up. Then the follow-up: the input clock is high for only 40 % of its period. That is a new specification, so the answer goes back to the first step: the output is now high for 14 ns of every 30, a 46.7 % duty cycle.
  8. The whole answer. Five steps, the RTL only the third of them: four of the five happen around the code. Every problem of this lesson is answered in this order.

The order is the point. Questions before the sketch catch the cases the one-line prompt leaves out: the input clock’s own duty cycle, whether N is always 3, the kind of reset, a chip or an FPGA. The sketch shows the plan while it is still cheap to change. After the code come the two steps that show you understand what you wrote: what synthesis will build from it, with its flip-flops and its longest path, and how you would know it works. A follow-up question is a change of specification, so it sends you back to the first step instead of into a patch of the code.

Say the numbers, not the adjectives

“Four flip-flops and three gates, one flip-flop on the falling edge” tells an interviewer what you know; “small and fast” does not. Every design in this lesson comes with the numbers Yosys gave for it.

Thirteen problem families, and the trap in each

Here are thirteen families of RTL whiteboard problems. Each has one idea that solves it and one trap that a first answer falls into, and the lesson of this track that builds it:

Family The trap Where this track builds it
Odd clock division, 50 % duty Counting on one edge gives (N − 1) / 2 cycles high in N This lesson, and clocking
Edge and pulse detectors Detecting an edge of an input from another clock without synchronizing it first Metastability
Sequence detectors Forgetting that one match can overlap the next The exercise below, and FSM coding
FIFOs and FIFO depth Telling full from empty, and sizing for the burst, not the average Synchronous FIFOs
Synchronizers Synchronizing each bit of a bus on its own Multi-bit crossings
Arbiters Fixed priority that starves the last requester Arbiters
Counters with load and enable Which wins when load and enable come together Counters and LFSRs
LFSRs Taps that do not give the maximal length Counters and LFSRs
Glitch-free clock switching A multiplexer that cuts a clock pulse short Clocking
Debouncers Sampling the raw input with no synchronizer Counters and LFSRs
Parity and CRC Bit order, and the register’s starting value DPI-C models
Rate matching Losing words when the rates differ for a burst Valid/ready
Pipelined arithmetic Keeping the valid bit in step with the data Pipelining

This lesson solves the first family in full and adds a fourteenth problem that sits beside them, the one-hot check, because it is small enough to verify exhaustively and its three possible answers differ in cost in a way you can measure. Read the trap column as a list of follow-up questions: ask each of them yourself once your first answer works.

Checkpoint

Check: The five steps of an answer · Problem families and their traps

  1. Question 1 of 2 The prompt is "divide a clock by 5 with a 50 % duty cycle". Which question do you ask before you draw anything?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Is the input clock itself high for 50 % of its period?

    Clarifying questions are about what the one-line prompt leaves open: the input clock's duty cycle, whether N is fixed, the reset, and the target. The input's duty cycle matters most here, because a 40 % input gave a 46.7 % output in the lesson's run.

  2. Question 2 of 2 An answer to "detect a rising edge of a push-button input" registers the input once and outputs in & ~in_q. Which trap of the table did it fall into?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The button is not synchronous to the clock, and the input was never synchronized

    An edge detector is correct only for a signal that is already synchronous to the clock. A button, a pin or a signal from another clock goes through a two-flip-flop synchronizer first, and the edge detector reads the second flip-flop.

An odd divider takes its half cycle from the falling edge

The output must be high for N / 2 input cycles, 1.5 when N is 3, and rising edges alone can only measure whole cycles. So the design uses both edges. On each rising edge a counter steps through 0 to N − 1 and a flip-flop, pos_q, is set for the first (N − 1) / 2 counts. On each falling edge a second flip-flop, neg_q, copies pos_q half a cycle later. Their OR is high for (N − 1) / 2 cycles plus a half: N / 2. Step through one period of the divide-by-3, edge by edge, as the run below drew it:

Interactive · step by step One period of the divide-by-3, edge by edge Each step is one edge of the input clock. Watch which flip-flop changes at it, and the output under them.
One period of the divide-by-3, edge by edge, step 7 of 7. Showing count (0), pos_q (rising edge) (1), neg_q (falling edge) (0), clk_out = pos_q | neg_q (high from 45 ns).next count below 1copied half a cycle oncount0pos_q (rising edge)1neg_q (falling edge)0clk_out = pos_q | neg_qhigh from 45 nsnext is 0

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. After reset. The reset has loaded count with 2, which is N - 1, and cleared both flip-flops, so clk_out is low. Starting at N - 1 means the first rising edge begins a whole period.
  2. 15 ns, rising edge. count steps from 2 to 0, and pos_q is loaded with whether that next count is below (N - 1) / 2 = 1: it is, so pos_q rises, and clk_out rises with it.
  3. 20 ns, falling edge. On the falling edge neg_q copies pos_q, half a cycle late. Both inputs of the OR are now 1.
  4. 25 ns, rising edge. count steps to 1, which is not below 1, so pos_q falls. neg_q still holds 1, so clk_out stays high: the OR changes only when the one input that moves is the only one that is 1.
  5. 30 ns, falling edge. neg_q copies the 0 and clk_out falls. It was high from 15 ns to 30 ns, 15 ns of a 30 ns period: 50 %.
  6. 35 ns, rising edge. count steps to 2 and pos_q stays 0. Nothing reaches the output; the falling edge at 40 ns copies the 0 again.
  7. 45 ns, rising edge. count wraps to 0 and pos_q rises: the next period starts 30 ns after the first. Each rising edge moved only the counter and pos_q, each falling edge only neg_q.

Two choices in div_odd.sv are worth saying aloud. pos_q is a flip-flop loaded from the next count, not a decode of the counter, because a decode of several changing bits can glitch: A short, unwanted pulse on the output of combinational logic while its inputs change at slightly different times. It is over before the next clock edge, so it harms only logic that uses the signal without that clock. More in “Counters, LFSRs, PWM and clock enables”, and this output is a clock. And the reset loads the counter with N − 1, not 0, so the first rising edge after reset starts a whole period. The OR gate itself is safe: each of its inputs changes on a different edge, and whenever one changes, the other holds still.

div_odd.sv: divide by any odd N with a 50 % duty cycle SV · hdl/div_odd.sv
`timescale 1ns / 1ps
// Divides clk by an odd N with a 50 % duty cycle. On each rising edge a counter steps through 0 .. N-1 and pos_q is set
// for the first (N - 1) / 2 counts; on each falling edge neg_q copies pos_q; clk_out is their OR, high for N / 2 cycles.
`default_nettype none
module div_odd #(parameter int N = 3) (
  input  wire  clk,
  input  wire  rst_n,              // asynchronous reset, active low
  output logic clk_out
);
  localparam int W = $clog2(N);
  localparam int HALF = (N - 1) / 2;
  logic [W-1:0] count, next_count;
  logic         pos_q, neg_q;

  assign next_count = (count == W'(N - 1)) ? '0 : count + 1'b1;

  always_ff @(posedge clk or negedge rst_n)
    if (!rst_n) begin
`ifdef RESET_TO_ZERO
      count <= '0;                         // the slip: for N of 5 or more the first pulse is cut short
`else
      count <= W'(N - 1);                  // the last count, so the first edge starts a whole period
`endif
      pos_q <= 1'b0;
    end else begin
      count <= next_count;
      pos_q <= (next_count < W'(HALF));    // a flip-flop, so pos_q cannot glitch
    end

  always_ff @(negedge clk or negedge rst_n)
    if (!rst_n) neg_q <= 1'b0;
    else        neg_q <= pos_q;

`ifdef RISING_ONLY
  assign clk_out = pos_q;                  // the slip: high for (N - 1) / 2 cycles in N, 33 % when N is 3
`else
  assign clk_out = pos_q | neg_q;          // each input of the OR changes while the other holds still
`endif
endmodule
`default_nettype wire

The model below runs the same rules in your browser, on a time grid of tenths of a period. Press Edit and set N to 5 or 7, or set DUTY to 40 to give the input clock a 40 % high time, and run it again:

The divider's model, drawn for two output periods Python · model/odd_divider.py
"""The divide-by-N clock of div_odd.sv, for any odd N: change N or DUTY and run it again.

After reset the counter holds N - 1. On each rising edge it steps to the next count, 0 after N - 1, and pos is set
for the first (N - 1) / 2 counts; on each falling edge neg copies pos; the output is pos OR neg. Time moves in tenths
of an input period, so a duty cycle such as 40 % is exact.
"""

N = 3        # an odd divisor: 3, 5, 7 ...
DUTY = 50    # the input clock's high time, in percent of its period
STEPS = 10   # time steps in one input period


def simulate(n, duty, periods):
    """The input clock and the output, one value per time step."""
    high_steps = duty * STEPS // 100
    count, pos, neg = n - 1, 0, 0
    clk, out = [], []
    for t in range(n * periods * STEPS):
        phase = t % STEPS
        if phase == 0:                                    # a rising edge
            count = 0 if count == n - 1 else count + 1
            pos = 1 if count < (n - 1) // 2 else 0
        if phase == high_steps:                           # a falling edge
            neg = pos
        clk.append(1 if phase < high_steps else 0)
        out.append(pos | neg)
    return clk, out


clk, out = simulate(N, DUTY, 4)
rises = [t for t in range(1, len(out)) if out[t] and not out[t - 1]]
period = rises[1] - rises[0]
high = out[rises[0]:rises[1]].count(1)
shown = slice(rises[0], rises[0] + 2 * period)            # two output periods, from a rising edge
draw = lambda wave: "".join("‾" if v else "_" for v in wave[shown])
print(f"N = {N}, input clock high for {DUTY} % of its period")
print("clk", draw(clk))
print("out", draw(out))
print(f"out: period {period / STEPS:g} input periods, high for {high / STEPS:g}, duty cycle {100 * high / period:.1f} %")

Output

N = 3, input clock high for 50 % of its period
clk ‾‾‾‾‾_____‾‾‾‾‾_____‾‾‾‾‾_____‾‾‾‾‾_____‾‾‾‾‾_____‾‾‾‾‾_____
out ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾_______________‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾_______________
out: period 3 input periods, high for 1.5, duty cycle 50.0 %

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 odd_divider.py

With N = 3 the output’s period is three input periods and it is high for 1.5 of them. Set DUTY to 40 and the high time becomes 1.4 periods, a 46.7 % duty cycle: the falling edge now comes at 40 % of the period, and neg_q follows it. That is the natural follow-up, and the answer is that this design passes on the input’s asymmetry; an exact 50 % output from an uneven input needs a clock-management block such as a PLL, or a faster clock to count with.

Key idea

An odd divider gets its half cycle from a flip-flop on the falling edge: pos_q is high for (N − 1) / 2 cycles, neg_q repeats it half a cycle later, and their OR is high for N / 2.

Checkpoint

Check: Odd division with a 50 % duty cycle

  1. Question 1 of 2 With N = 7, for how many input periods is pos_q high in each output period?

    Type a number.

    A hint

    pos_q is set for the first (N - 1) / 2 counts.

    Show the answer: Question 1 of 2

    Answer: 3 periods

    (7 - 1) / 2 = 3. neg_q adds half a period on the falling edge, so clk_out is high for 3.5 of the 7 periods: 50 %.

  2. Question 2 of 2 The divider runs at N = 3 on a 10 ns input clock that is high for only 3 ns of each period. For how many nanoseconds is clk_out high in each output period?

    Type a number.

    A hint

    pos_q is high from one rising edge to the next; neg_q holds the output up until the falling edge after that.

    Show the answer: Question 2 of 2

    Answer: 13 ns

    pos_q is high for one whole period, 10 ns, and neg_q copies it at the falling edge, 3 ns after each rising edge, so the OR stays high for 10 + 3 = 13 ns of 30. With a 40 % input the lesson's run measured 14 ns the same way.

A 30-line testbench, run at three values of N, catches three slips

A testbench for a clock divider does not need waveforms: it can time the output. tb_div.sv records the time of each rising edge of clk_out, checks that each period is N × 10 ns and each high time N × 5 ns on a 10 ns clock, prints the first two failures and stops with $fatal if anything was wrong. N is a parameter of the testbench too, so one file tests every divisor. Here it runs at N = 3 and 7 on Icarus Verilog and at N = 5 on Verilator:

One testbench, three divisors, two simulators

hdl/tb_div.sv

// Checks every period of clk_out (N × 10 ns) and every high time (N × 5 ns) for a 10 ns input clock.
`timescale 1ns / 1ps
module tb_div #(parameter int N = 3);
  logic clk = 0, rst_n;
  wire  clk_out;
  div_odd #(.N(N)) dut (.clk, .rst_n, .clk_out);
`ifdef INPUT_40
  always begin clk = 1; #4; clk = 0; #6; end    // the same 10 ns clock, high for 40 % of it
`else
  always #5 clk = ~clk;
`endif
  time rose = 0;
  int  periods = 0, wrong = 0;
  task automatic fail(string what, time got);
    wrong++;
    if (wrong <= 2) $display("%s %0d ns at %0d ns", what, got, $time);
  endtask
  always @(posedge clk_out) begin
    if (rose != 0 && $time - rose != 10 * N) fail("period", $time - rose);
    if (rose != 0) periods++;
    rose = $time;
  end
  always @(negedge clk_out) if (rose != 0 && 2 * ($time - rose) != 10 * N) fail("high", $time - rose);
  initial begin
    rst_n = 0;
    #12 rst_n = 1;
    #(200 * N) $display("N = %0d: %0d periods checked, %0d wrong: %s", N, periods, wrong, wrong != 0 ? "FAIL" : "PASS");
    if (wrong != 0) $fatal(1, "clk_out is not a 50 %% divide-by-%0d", N);
    $finish;
  end
endmodule

Commands · hdl/div_session.py

from session import run

# Divide by 3 and by 7 on Icarus Verilog, by 5 on Verilator, all with the same testbench.
run("""
iverilog -g2012 -o div3.vvp div_odd.sv tb_div.sv
vvp -n div3.vvp
iverilog -g2012 -Ptb_div.N=7 -o div7.vvp div_odd.sv tb_div.sv
vvp -n div7.vvp
verilator --binary -j 0 --quiet -GN=5 --top tb_div div_odd.sv tb_div.sv
obj_dir/Vtb_div +verilator+quiet
""")

Output

# Icarus Verilog 13.0 (stable) · Verilator 5.052
$ iverilog -g2012 -o div3.vvp div_odd.sv tb_div.sv
$ vvp -n div3.vvp
N = 3: 19 periods checked, 0 wrong: PASS
tb_div.sv:29: $finish called at 612000 (1ps)
$ iverilog -g2012 -Ptb_div.N=7 -o div7.vvp div_odd.sv tb_div.sv
$ vvp -n div7.vvp
N = 7: 19 periods checked, 0 wrong: PASS
tb_div.sv:29: $finish called at 1412000 (1ps)
$ verilator --binary -j 0 --quiet -GN=5 --top tb_div div_odd.sv tb_div.sv
$ obj_dir/Vtb_div +verilator+quiet
N = 5: 19 periods checked, 0 wrong: PASS
- tb_div.sv:29: Verilog $finish

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 div_session.py

All three pass with 19 periods checked. Running more than one value of N is a small parameter sweep: A regression that builds and tests a design at many values of its parameters, such as every depth and width of a FIFO, with each combination in a build of its own., and it matters here. The same testbench, given three slipped builds of the design through -D defines, fails each of them:

The rising edge alone, a reset to 0, and a 40 % input clock Python · hdl/slips_session.py
from session import run

# Three slips the same testbench catches: the rising edge alone, a counter reset to 0 (caught only when N is 5 or
# more), and an input clock that is high for 40 % of its period.
run("""
iverilog -g2012 -DRISING_ONLY -o rising.vvp div_odd.sv tb_div.sv
!vvp -n rising.vvp
iverilog -g2012 -DRESET_TO_ZERO -o zero3.vvp div_odd.sv tb_div.sv
vvp -n zero3.vvp
iverilog -g2012 -DRESET_TO_ZERO -Ptb_div.N=5 -o zero5.vvp div_odd.sv tb_div.sv
!vvp -n zero5.vvp
iverilog -g2012 -DINPUT_40 -o duty.vvp div_odd.sv tb_div.sv
!vvp -n duty.vvp
""")

Output

# Icarus Verilog 13.0 (stable)
$ iverilog -g2012 -DRISING_ONLY -o rising.vvp div_odd.sv tb_div.sv
$ vvp -n rising.vvp
high 10 ns at 25 ns
high 10 ns at 55 ns
N = 3: 19 periods checked, 20 wrong: FAIL
FATAL: tb_div.sv:28: clk_out is not a 50 % divide-by-3
       Time: 612000  Scope: tb_div
[exit status 1]
$ iverilog -g2012 -DRESET_TO_ZERO -o zero3.vvp div_odd.sv tb_div.sv
$ vvp -n zero3.vvp
N = 3: 19 periods checked, 0 wrong: PASS
tb_div.sv:29: $finish called at 612000 (1ps)
$ iverilog -g2012 -DRESET_TO_ZERO -Ptb_div.N=5 -o zero5.vvp div_odd.sv tb_div.sv
$ vvp -n zero5.vvp
high 15 ns at 30 ns
period 40 ns at 55 ns
N = 5: 20 periods checked, 2 wrong: FAIL
FATAL: tb_div.sv:28: clk_out is not a 50 % divide-by-5
       Time: 1012000  Scope: tb_div
[exit status 1]
$ iverilog -g2012 -DINPUT_40 -o duty.vvp div_odd.sv tb_div.sv
$ vvp -n duty.vvp
high 14 ns at 34 ns
high 14 ns at 64 ns
N = 3: 19 periods checked, 20 wrong: FAIL
FATAL: tb_div.sv:28: clk_out is not a 50 % divide-by-3
       Time: 612000  Scope: tb_div
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 slips_session.py

  • The rising edge alone (RISING_ONLY) gives a high time of 10 ns in every period of 30: the 33 % answer.
  • A counter reset to 0 (RESET_TO_ZERO) passes at N = 3 and fails at N = 5: the first pulse after reset is 15 ns instead of 25, because the first rising edge lands in the middle of a period. A testbench run only at N = 3 would have passed this bug.
  • A 40 % input clock (INPUT_40) makes every high time 14 ns, as the model predicted. The design is right; the check reports that the specification’s assumption about the input no longer holds.

Testing one parameter value

Wrong: proving a parameterised design with its default value alone. The reset slip above is invisible at N = 3. Right: run the testbench at the default and at the next values up, here 5 and 7, which takes one more command each.

Checkpoint

Check: A short testbench at several parameter values

  1. Question 1 of 2 Why did the build that resets the counter to 0 pass at N = 3 and fail at N = 5?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: At N = 3 a period starts when the counter wraps to 0, so a reset to 0 still gives a whole first pulse; at N = 5 the first edge already sets pos_q at count 1

    The slip cut the first pulse to 15 ns at N = 5, instead of 25 ns, because the first rising edge landed in the middle of a period. N = 3 hides it, which is why one testbench run at several values of N is worth its few extra commands.

  2. Question 2 of 2 At N = 7 on its 10 ns input clock, what does tb_div.sv require of every output period?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: A period of 70 ns and a high time of 35 ns

    The testbench times the output instead of drawing it: every period must be N × 10 ns and every high time N × 5 ns, here 70 and 35 ns, and the first two failures are printed.

A one-hot check in one line, and three ways to build it

The second problem: given a bus x, say whether exactly one bit is set, as a one-hot: A code with one bit per choice and exactly one bit set, such as 0100 for choice 2 of four. A decoder makes it from a binary number with a shift; $onehot checks that a value is one. More in “Muxes, encoders and round-robin arbiters” state register or a grant vector must be. Subtracting 1 from x clears its lowest set bit and sets the bits below it, so x & (x − 1) is zero exactly when x has at most one bit set. That is SystemVerilog’s $onehot0; adding x ≠ 0 rules out the empty word and gives $onehot:

onehot_check.sv: at most one bit, and exactly one SV · hdl/onehot_check.sv
// Is exactly one bit of x set? x & (x - 1) clears the lowest set bit, so it is zero when x has at most one bit set;
// x != 0 then rules out the empty word.
`default_nettype none
module onehot_check #(parameter int W = 8) (
  input  wire [W-1:0] x,
  output logic        at_most_one,   // zero or one bit set ($onehot0)
  output logic        exactly_one    // one bit set ($onehot)
);
  assign at_most_one = (x & (x - 1'b1)) == '0;
`ifdef NO_ZERO_TERM
  assign exactly_one = at_most_one;                // the slip: 0 counts as one-hot
`else
  assign exactly_one = at_most_one && (x != '0);
`endif
endmodule
`default_nettype wire

An 8-bit input has only 256 values, so the testbench tries them all, an exhaustive test: A test that applies every possible input value and checks each result, practical while the input has few bits: 256 values for an 8-bit word, 65,536 for a 16-bit one., against a reference that counts the ones in a loop: a different method, so a shared mistake is unlikely. The session also builds the version without the x ≠ 0 term, then checks the three forms of onehot_forms.sv, the trick, a count of the ones and a tree, against each other for all 65,536 values of 16 bits:

All 256 values, the slip, and three forms over 65,536 values

hdl/tb_onehot.sv

// Every 8-bit value against a reference that counts the ones in a loop.
module tb_onehot;
  logic [7:0] x;
  wire        at_most_one, exactly_one;
  onehot_check #(.W(8)) dut (.x, .at_most_one, .exactly_one);
  int wrong = 0, ones;
  initial begin
    for (int v = 0; v < 256; v++) begin
      x = 8'(v);
      ones = 0;
      for (int i = 0; i < 8; i++) ones += int'(x[i]);
      #1;
      if (exactly_one !== (ones == 1) || at_most_one !== (ones <= 1)) begin
        wrong++;
        $display("x = %b: exactly_one %b at_most_one %b, but %0d bits are set", x, exactly_one, at_most_one, ones);
      end
    end
    $display("256 values checked, %0d wrong: %s", wrong, wrong != 0 ? "FAIL" : "PASS");
    if (wrong != 0) $fatal(1, "the one-hot check is wrong");
    $finish;
  end
endmodule

hdl/onehot_forms.sv

// Three ways to say "exactly one bit of x is set", for synthesis to compare.
`default_nettype none
module onehot_trick #(parameter int W = 8) (input wire [W-1:0] x, output logic y);
  assign y = ((x & (x - 1'b1)) == '0) && (x != '0);
endmodule

module onehot_count #(parameter int W = 8) (input wire [W-1:0] x, output logic y);
  always_comb begin
    int ones;
    ones = 0;
    for (int i = 0; i < W; i++) ones += int'(x[i]);
    y = (ones == 1);
  end
endmodule

// A tree of pairs: each node knows whether any bit below it is set, and whether more than one is.
module onehot_tree #(parameter int W = 8) (input wire [W-1:0] x, output logic y);
  logic [2*W-2:0] any, many;   // heap order: node k has children 2k+1 and 2k+2; the leaves are W-1 .. 2W-2
  always_comb begin
    for (int i = 0; i < W; i++) begin
      any[W-1+i]  = x[i];
      many[W-1+i] = 1'b0;
    end
    for (int k = W-2; k >= 0; k--) begin
      any[k]  = any[2*k+1] | any[2*k+2];
      many[k] = many[2*k+1] | many[2*k+2] | (any[2*k+1] & any[2*k+2]);
    end
    y = any[0] & ~many[0];
  end
endmodule
`default_nettype wire

hdl/tb_forms.sv

// The three one-hot checks of onehot_forms.sv at 16 bits, against a loop that counts the ones, for all 65,536 values.
module tb_forms;
  logic [15:0] x;
  wire  trick, count, tree;
  onehot_trick #(.W(16)) u_trick (.x, .y(trick));
  onehot_count #(.W(16)) u_count (.x, .y(count));
  onehot_tree  #(.W(16)) u_tree  (.x, .y(tree));
  int wrong = 0, hits = 0, ones;
  initial begin
    for (int v = 0; v < 65536; v++) begin
      x = 16'(v);
      ones = 0;
      for (int i = 0; i < 16; i++) ones += int'(x[i]);
      #1;
      if (ones == 1) hits++;
      if (trick !== (ones == 1) || count !== (ones == 1) || tree !== (ones == 1)) wrong++;
    end
    $display("65536 values, %0d of them one-hot: %0d wrong: %s", hits, wrong, wrong != 0 ? "FAIL" : "PASS");
    if (wrong != 0) $fatal(1, "the forms disagree");
    $finish;
  end
endmodule

Commands · hdl/onehot_session.py

from session import run

# The whiteboard answer for all 256 values on both simulators, the version without the x != 0 term, then the three
# forms of onehot_forms.sv for all 65,536 values of 16 bits.
run("""
iverilog -g2012 -o onehot.vvp onehot_check.sv tb_onehot.sv
vvp -n onehot.vvp
verilator --binary -j 0 --quiet --top tb_onehot onehot_check.sv tb_onehot.sv
obj_dir/Vtb_onehot +verilator+quiet
iverilog -g2012 -DNO_ZERO_TERM -o slip.vvp onehot_check.sv tb_onehot.sv
!vvp -n slip.vvp
verilator --binary -j 0 --quiet --top tb_forms -Mdir obj_forms onehot_forms.sv tb_forms.sv
obj_forms/Vtb_forms +verilator+quiet
""")

Output

# Icarus Verilog 13.0 (stable) · Verilator 5.052
$ iverilog -g2012 -o onehot.vvp onehot_check.sv tb_onehot.sv
$ vvp -n onehot.vvp
256 values checked, 0 wrong: PASS
tb_onehot.sv:20: $finish called at 256 (1s)
$ verilator --binary -j 0 --quiet --top tb_onehot onehot_check.sv tb_onehot.sv
$ obj_dir/Vtb_onehot +verilator+quiet
256 values checked, 0 wrong: PASS
- tb_onehot.sv:20: Verilog $finish
$ iverilog -g2012 -DNO_ZERO_TERM -o slip.vvp onehot_check.sv tb_onehot.sv
$ vvp -n slip.vvp
x = 00000000: exactly_one 1 at_most_one 1, but 0 bits are set
256 values checked, 1 wrong: FAIL
FATAL: tb_onehot.sv:19: the one-hot check is wrong
       Time: 256  Scope: tb_onehot
[exit status 1]
$ verilator --binary -j 0 --quiet --top tb_forms -Mdir obj_forms onehot_forms.sv tb_forms.sv
$ obj_forms/Vtb_forms +verilator+quiet
65536 values, 16 of them one-hot: 0 wrong: PASS
- tb_forms.sv:20: Verilog $finish

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 onehot_session.py

The slip fails on exactly one value of 256, x = 0, which a handful of hand-picked tests could easily miss and an exhaustive one cannot. The three forms agree on all 65,536 values of 16 bits, 16 of which are one-hot. Equal outputs are not equal hardware, though, and that is what the next section measures.

Programmer Calculator (Bitwise) Work x & (x − 1) by hand on a few words, such as 0b0100 and 0b0110, and watch the lowest set bit clear.

Checkpoint

Check: The one-hot check and its exhaustive test

  1. Question 1 of 2 The slip without the x != 0 term was wrong for how many of the 256 values of an 8-bit word?

    Type a number.

    Show the answer: Question 1 of 2

    Answer: 1 value

    Only x = 0: 0 & (0 - 1) is 0, so the trick alone calls the empty word one-hot. The exhaustive test found it at once; a few hand-picked one-hot values would never have tried 0.

  2. Question 2 of 2 What is the value of y?

    Read the code, then choose one answer.

    logic [7:0] x, y;
    x = 8'b0110_1000;
    y = x & (x - 1'b1);
    Show the answer: Question 2 of 2

    Answer: 8'b0110_0000

    Subtracting 1 turns the lowest 1 into a 0 and the 0s below it into 1s; ANDing with x clears exactly the lowest set bit, so the result is zero only for words with at most one bit set.

Explain timing, area and reset aloud, with the numbers synthesis gives

After the code comes its cost, and Yosys answers in seconds: stat counts the cells and ltp -noff reports the longest path through gates between flip-flops and ports, the design’s logic depth: The number of gates on the longest path between flip-flops or ports. Each gate adds its delay, so a deeper path needs a slower clock; Yosys's ltp command reports it.. The session below synthesises the divider at N = 3 and 7, then each one-hot form at 8, 32 and 64 bits:

Cells, flip-flops and the longest path through gates Python · hdl/area_session.py
from session import run

# What synthesis builds: the divider for N = 3 and 7, then the three one-hot checks at 8, 32 and 64 bits, each with
# its cells and its longest path through gates (Yosys's ltp -noff).
run("""
yosys -q -p "read_verilog -sv div_odd.sv; synth -top div_odd; tee -q -o div3.txt stat"
yosys -q -p "read_verilog -sv div_odd.sv; chparam -set N 7 div_odd; synth -top div_odd; tee -q -o div7.txt stat"
grep -H -E "cells|DFF" div3.txt div7.txt
for f in trick count tree; do for w in 8 32 64; do yosys -q -p "read_verilog -sv onehot_forms.sv; chparam -set W $w onehot_$f; synth -top onehot_$f; tee -q -o $f$w.txt stat; tee -q -a $f$w.txt ltp -noff"; done; done
python3 summary.py trick8.txt trick32.txt trick64.txt count8.txt count32.txt count64.txt tree8.txt tree32.txt tree64.txt
""")

Output

# Yosys 0.69
$ yosys -q -p "read_verilog -sv div_odd.sv; synth -top div_odd; tee -q -o div3.txt stat"
$ yosys -q -p "read_verilog -sv div_odd.sv; chparam -set N 7 div_odd; synth -top div_odd; tee -q -o div7.txt stat"
$ grep -H -E "cells|DFF" div3.txt div7.txt
div3.txt:        7 cells
div3.txt:        1   $_DFF_NN0_
div3.txt:        2   $_DFF_PN0_
div3.txt:        1   $_DFF_PN1_
div7.txt:       14 cells
div7.txt:        1   $_DFF_NN0_
div7.txt:        2   $_DFF_PN0_
div7.txt:        2   $_DFF_PN1_
$ for f in trick count tree; do for w in 8 32 64; do yosys -q -p "read_verilog -sv onehot_forms.sv; chparam -set W $w onehot_$f; synth -top onehot_$f; tee -q -o $f$w.txt stat; tee -q -a $f$w.txt ltp -noff"; done; done
$ python3 summary.py trick8.txt trick32.txt trick64.txt count8.txt count32.txt count64.txt tree8.txt tree32.txt tree64.txt
onehot_trick  8 bits:  19 cells, longest path  8 gates
onehot_trick 32 bits:  92 cells, longest path 32 gates
onehot_trick 64 bits: 188 cells, longest path 64 gates
onehot_count  8 bits:  18 cells, longest path  6 gates
onehot_count 32 bits: 112 cells, longest path 17 gates
onehot_count 64 bits: 273 cells, longest path 21 gates
onehot_tree   8 bits:  19 cells, longest path  5 gates
onehot_tree  32 bits:  91 cells, longest path  8 gates
onehot_tree  64 bits: 187 cells, longest path  9 gates

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 area_session.py

The divider is 7 cells at N = 3: four flip-flops (two counter bits, pos_q and neg_q; one counter bit resets to 1, because N − 1 is 2) and three gates. At N = 7 it is 14 cells with five flip-flops: one more counter bit, and the counter’s logic. The one-hot forms tell the more interesting story:

Form Cells at 64 bits; longest path at 8, 32, 64 bits What to say aloud
x & (x − 1) 188 cells; 8, 32 and 64 gates A borrow ripples through every bit: depth grows with the width
Count the ones 273 cells; 6, 17 and 21 gates An adder tree: shallow, but the largest
A tree of pairs 187 cells; 5, 8 and 9 gates Each node keeps “any bit set” and “two or more”: depth grows with log₂ of the width

At 64 bits the trick and the tree cost the same, 188 and 187 cells, but the trick’s longest path is 64 gates against the tree’s 9, so only the tree would meet a fast clock. That is the answer to the follow-up “your check is on a 64-bit bus at 1 GHz”: keep x & (x − 1) for narrow buses and for its one line of code, and know the tree. Say the reset behaviour as plainly: the divider’s output is low during reset, and its first period after reset is whole, which the N = 5 run proved.

Key idea

Explain a design with three numbers from synthesis: its flip-flops, its cells and its longest path. The one-hot trick and the tree both took about 187 cells at 64 bits, and their paths were 64 and 9 gates.

Checkpoint

Check: Flip-flops, cells and logic depth, said aloud

  1. Question 1 of 2 At 64 bits, how many gates shorter was the tree's longest path than that of x & (x - 1), in the lesson's run?

    Type a number.

    A hint

    Read both paths from the table, at 64 bits.

    Show the answer: Question 1 of 2

    Answer: 55 gates

    The trick's longest path was 64 gates and the tree's 9: 55 fewer, for about the same size, 188 and 187 cells. The trick's borrow ripples through every bit; the tree's depth grows with log2 of the width.

  2. Question 2 of 2 How many flip-flops did Yosys keep for the divider at N = 3?

    Type a number.

    Show the answer: Question 2 of 2

    Answer: 4 flip-flops

    Two counter bits, pos_q and neg_q: four flip-flops, one of them clocked on the falling edge, and three gates, 7 cells in all. At N = 7 a third counter bit makes five.

Four coding slips that lint finds before an interviewer does

Some slips simulate without complaint and build the wrong hardware, so they are the first things to look for in your own whiteboard RTL. edge_count.sv has four of them: a blocking assignment in a clocked block, eight bits assigned into four, an if without a final else in a combinational block, and a case with no default. Verilator’s lint with every warning on, --lint-only -Wall, names all four:

Six warnings for four slips, and none for the fixed build

hdl/edge_count.sv

// A rising-edge counter with four slips that interviewers listen for; -DFIXED builds the corrected lines.
`default_nettype none
module edge_count (
  input  wire        clk,
  input  wire        rst_n,
  input  wire        din,       // already synchronous to clk
  input  wire        load,
  input  wire  [7:0] len,       // a field of a register: only its low 4 bits are a count
  input  wire  [1:0] mode,
  output logic       rise,
  output logic [3:0] count,
  output logic       busy,
  output logic       led
);
  logic din_q;
`ifndef FIXED
  always_ff @(posedge clk) din_q = din;                // slip 1: a blocking assignment in a clocked block
  always_ff @(posedge clk or negedge rst_n)
    if (!rst_n)    count <= '0;
    else if (load) count <= len;                       // slip 2: 8 bits into 4, without a word
    else if (rise) count <= count + 4'd1;
  always_comb
    if (mode == 2'd1)      busy = 1'b1;                // slip 3: no final else, so busy must remember: a latch
    else if (mode == 2'd2) busy = 1'b0;
  always_comb
    case (mode)                                        // slip 4: no default, so modes 1 and 2 leave led alone
      2'd0: led = 1'b0;
      2'd3: led = 1'b1;
    endcase
`else
  always_ff @(posedge clk or negedge rst_n)
    if (!rst_n) din_q <= 1'b0;
    else        din_q <= din;
  always_ff @(posedge clk or negedge rst_n)
    if (!rst_n)    count <= '0;
    else if (load) count <= len[3:0];
    else if (rise) count <= count + 4'd1;
  wire unused_len = |len[7:4];                         // the other field of the register, read elsewhere
  always_comb busy = (mode == 2'd1);
  always_comb
    case (mode)
      2'd3:    led = 1'b1;
      default: led = 1'b0;
    endcase
`endif
  assign rise = din & ~din_q;
endmodule
`default_nettype wire

Commands · hdl/lint_session.py

from session import run

# Verilator's lint with every warning on, for the edge counter as written and with -DFIXED; the first run's messages
# go to lint.txt, and grep keeps the first line of each.
run("""
!verilator --lint-only -Wall edge_count.sv > lint.txt 2>&1
grep "^%" lint.txt
verilator --lint-only -Wall --quiet -DFIXED edge_count.sv
""")

Output

# Verilator 5.052
$ verilator --lint-only -Wall edge_count.sv > lint.txt 2>&1
[exit status 1]
$ grep "^%" lint.txt
%Warning-WIDTHTRUNC: edge_count.sv:20:26: Operator ASSIGNDLY expects 4 bits on the Assign RHS, but Assign RHS's VARREF 'len' generates 8 bits.
%Warning-BLKSEQ: edge_count.sv:17:34: Blocking assignment '=' in sequential logic process
%Warning-UNUSEDSIGNAL: edge_count.sv:8:22: Bits of signal are not used: 'len'[7:4]
%Warning-CASEINCOMPLETE: edge_count.sv:26:5: Case values incompletely covered (example pattern 0x1)
%Warning-LATCH: edge_count.sv:22:3: Latch inferred for signal 'busy' (not all control paths of combinational always assign a value)
%Warning-LATCH: edge_count.sv:25:3: Latch inferred for signal 'led' (not all control paths of combinational always assign a value)
%Error: Exiting due to 6 warning(s)
$ verilator --lint-only -Wall --quiet -DFIXED edge_count.sv

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 lint_session.py

Six warnings for four slips: the width slip also leaves the field’s upper bits unread (UNUSEDSIGNAL), and the case without a default is reported twice, as CASEINCOMPLETE and as a latch: A storage element that passes its input while its enable is on and holds its last value while the enable is off. A combinational block with a path that assigns nothing describes one. More in “Combinational logic with assign and always_comb”. The fixed build assigns len[3:0], writes busy as one expression with no path left out, gives the case a default and uses <= in the clocked block. It lints with no warning at all. The upper bits of len belong to another field of the register, so the fix reads them into a wire named unused_len: Verilator’s documentation says the name “unused” is recognised and suppresses the warning, which keeps the reason visible in the code instead of in a switch on the command line.

A latch from a missing else

Wrong: if (mode == 1) busy = 1; else if (mode == 2) busy = 0; in an always_comb, which leaves busy unassigned for modes 0 and 3, so it must remember: a latch. Right: give every path a value, here busy = (mode == 1);, or start the block with a default assignment.

Checkpoint

Check: Coding slips that lint finds

  1. Question 1 of 2 What does synthesis build for busy from this block?

    Read the code, then choose one answer.

    always_comb
      if (mode == 2'd1)      busy = 1'b1;
      else if (mode == 2'd2) busy = 1'b0;
    Show the answer: Question 1 of 2

    Answer: A latch, because modes 0 and 3 assign nothing, so busy must keep its old value

    Every path of a combinational block must assign every output it drives. Writing busy = (mode == 2'd1), or a default assignment at the top of the block, removes the latch, as the fixed build did.

  2. Question 2 of 2 Why did Verilator report six warnings for edge_count.sv's four slips?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The width slip also left the field's upper bits unread, and the case without a default was reported as both CASEINCOMPLETE and LATCH

    One slip can trip more than one rule: an 8-bit field into a 4-bit counter truncates (WIDTHTRUNC) and leaves four bits unread (UNUSEDSIGNAL), and an incomplete case both misses values (CASEINCOMPLETE) and infers a latch.

Practise: an overlapping sequence detector

The exercise is a third family from the table: a sequence detector: A state machine that reads a serial input one bit per clock cycle and raises its output when the latest bits match a pattern. An overlapping detector lets the last bits of one match start the next. for the pattern 1101 on a serial input, modelled in Python as the RTL would build it, a state that counts the bits matched so far and a table of next states. The starter has two classic slips in its table; the sample tests include an overlap, a run of ones and a pattern broken one bit early.

Exercise · Medium · Python

An overlapping 1101 detector, cycle by cycle

Model a sequence detector the way the RTL would build it: a Mealy machine that reads one bit per clock cycle and outputs 1 in the cycle whose bit completes the pattern 1101. Matches may overlap, so the last 1 of one match can be the first 1 of the next: 1101101 holds two matches, ending at positions 3 and 6.

Write detect(bits), which takes the bits as a string of 0 and 1 characters, oldest first, and returns a string of the same length: 1 where a match ends, 0 everywhere else. The starter keeps a state, the number of bits of 1101 that the latest bits match, in a table of next states, and reads the table once per bit. Its table has two classic slips: after a match it starts again from nothing, and a third 1 in a row throws away the two before it.

The sample tests include the overlap, a run of ones, a pattern broken one bit before its end, and the empty input.

Starter code · detector.py

# The state is how many bits of 1101 the latest bits match: 0, 1 ("1"), 2 ("11") or 3 ("110").
NEXT = {
    (0, "0"): 0, (0, "1"): 1,
    (1, "0"): 0, (1, "1"): 2,
    (2, "0"): 3, (2, "1"): 0,
    (3, "0"): 0, (3, "1"): 0,
}


def detect(bits):
    """1 in each cycle whose bit completes 1101, overlaps allowed; 0 in every other cycle."""
    state, out = 0, []
    for b in bits:
        out.append("1" if state == 3 and b == "1" else "0")
        state = NEXT[(state, b)]
    return "".join(out)
The sample tests · test_detector.py
from detector import detect


def test_one_match():
    """the pattern alone: the output rises in the cycle of its last bit"""
    assert detect("1101") == "0001"


def test_overlapping_matches():
    """the last 1 of a match starts the next one"""
    assert detect("1101101") == "0001001"


def test_a_run_of_ones():
    """111 still ends in 11, so 11101 is a match"""
    assert detect("11101") == "00001"


def test_broken_one_bit_early():
    """1100 breaks the pattern; the detector starts again and finds the match that follows"""
    assert detect("110011101") == "000000001"


def test_no_match_and_no_input():
    """0101 is not 1101, 110 is not finished, and an empty input gives an empty output"""
    assert detect("0101") == "0000"
    assert detect("110") == "000"
    assert detect("") == ""
A hint

After a match the latest bits are 1101. The longest ending of 1101 that is also a beginning of 1101 is 1, so the next state after a match is 1, not 0. Check the other seven entries the same way: for state 2 (11) and a 1, the latest bits are 111, whose longest such ending is 11.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Then check yourself on the seven ideas of this lesson, from the five steps to lint, in a session that leads with your weakest one:

Practice that adapts to you

Practise this lesson

14 questions about 7 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 14 In the five-step answer of the lesson, what comes right after writing the RTL?

    Choose one answer.

    Show the answer: Question 1 of 14

    Answer: Explaining timing, area and reset

    Clarify, sketch, write, explain, verify. The steps after the code, what it costs and how you would know it works, are the ones a first answer most easily leaves out.

  2. Question 2 of 14 Halfway through your answer the interviewer adds "and N can be any odd number". What should you do first?

    Choose one answer.

    Show the answer: Question 2 of 14

    Answer: Go back to clarifying, since the specification changed, and then generalise the counter and the pos_q rule

    Treat a follow-up as a change of specification: clarify it, adjust the sketch, then the code. div_odd.sv covers every odd N with one parameter, and the testbench took N as a parameter too.

  3. Question 3 of 14 Which trap belongs to sequence detectors?

    Choose one answer.

    Show the answer: Question 3 of 14

    Answer: Forgetting that one match can overlap the next

    A sequence detector that starts again from nothing after a match misses overlapping matches; the exercise of this lesson has that slip in its starter.

  4. Question 4 of 14 A producer writes a burst of 64 words, one per cycle, and the consumer reads one word every other cycle. A first answer sizes the FIFO from the average rates and calls it fine. Which trap is that?

    Choose one answer.

    Show the answer: Question 4 of 14

    Answer: Sizing for the average instead of the burst

    FIFO depth comes from the worst burst: the words that arrive while the consumer cannot take them. Here 64 - 32 = 32 words wait at the end of the burst; the FIFO lesson of this track works such numbers.

  5. Question 5 of 14 Why is the OR of pos_q and neg_q glitch-free?

    Choose one answer.

    Show the answer: Question 5 of 14

    Answer: Each input changes on a different clock edge, and whenever one changes the other holds still

    A glitch needs two inputs changing at about the same moment. pos_q and neg_q are flip-flops on opposite edges, so at every change of the OR only one input moves.

  6. Question 6 of 14 The divider runs at N = 9 on a 10 ns clock with a 50 % duty cycle. For how many nanoseconds is clk_out high in each period?

    Type a number.

    Show the answer: Question 6 of 14

    Answer: 45 ns

    pos_q is high for (9 - 1) / 2 = 4 periods, 40 ns, and neg_q adds half a period, 5 ns: 45 ns of a 90 ns period, 50 %. The model of the lesson prints the same for N = 9: high for 4.5 input periods.

  7. Question 7 of 14 Why run the divider's testbench at N = 3, 5 and 7 instead of N = 3 alone?

    Choose one answer.

    Show the answer: Question 7 of 14

    Answer: A slip can hide at one value of a parameter, as the reset to 0 did at N = 3

    A parameterised design is many designs. Running the default and the next values up cost two more commands here and caught a slip that N = 3 cannot show.

  8. Question 8 of 14 The INPUT_40 run failed every high-time check, with 14 ns instead of 15. A colleague wants to change the design until the test passes. What do you say?

    Choose one answer.

    Show the answer: Question 8 of 14

    Answer: The design is right; the failure says the input no longer meets the specification's assumption, so the specification and its check must change, or the clock source must

    A failing check is information. Here it said that the output's 50 % depends on the input's 50 %, which belongs in the specification as a requirement on the clock source.

  9. Question 9 of 14 Which expression is true exactly when x has one bit set?

    Choose one answer.

    Show the answer: Question 9 of 14

    Answer: ((x & (x - 1)) == 0) && (x != 0)

    The trick removes the lowest set bit; a word with no bit left was zero-or-one-hot, and x != 0 makes it exactly one-hot, $onehot in SystemVerilog.

  10. Question 10 of 14 The one-hot check is 16 bits wide. Is an exhaustive test still practical?

    Choose one answer.

    Show the answer: Question 10 of 14

    Answer: Yes, 65,536 values, which the lesson's run checked for three forms at once

    Exhaustive testing is the strongest check there is while the input space is small: 256 values at 8 bits, 65,536 at 16. At 64 bits it is out of reach, and an equivalence proof against a trusted form takes its place.

  11. Question 11 of 14 What does Yosys's ltp -noff report?

    Choose one answer.

    Show the answer: Question 11 of 14

    Answer: The longest path through gates, counting cells between flip-flops and ports

    ltp counts the gates on the longest path, 64 for the trick at 64 bits and 9 for the tree. Gates times their delay, plus the flip-flops' own times, is what a timing report adds up.

  12. Question 12 of 14 Which sentence explains the divider's reset behaviour the way an interviewer wants it?

    Choose one answer.

    Show the answer: Question 12 of 14

    Answer: During reset the output is low; the counter starts at N - 1, so the first rising edge after reset begins a whole period

    Reset behaviour is two facts: the outputs during reset, and the first cycle after it. The divider's first pulse after reset is whole because the counter starts at N - 1.

  13. Question 13 of 14 Which Verilator warning names a blocking assignment in a clocked block?

    Choose one answer.

    Show the answer: Question 13 of 14

    Answer: BLKSEQ

    BLKSEQ flags din_q = din inside always_ff; the fix is din_q <= din, with a reset, so that every reader sees the value from before the edge.

  14. Question 14 of 14 The fixed build reads only len[3:0]; bits 7:4 belong to another field. How did it keep -Wall clean without a switch on the command line?

    Choose one answer.

    Show the answer: Question 14 of 14

    Answer: It reads the upper bits into a wire named unused_len, a name Verilator recognises as intentionally unused

    A waiver belongs next to the code it excuses. A wire with "unused" in its name, or a lint_off comment around one line, says why; a global switch hides every future case too.

Interview questions

Warm-up (fresher): why does an odd clock divider with a 50 % duty cycle need the falling edge of the clock?

Because the output must be high for N / 2 input periods, and N / 2 is not a whole number when N is odd: 1.5 periods for a divide-by-3. Logic clocked on rising edges alone changes only at whole periods, so it can give 1 or 2 periods high in 3, a 33 % or 67 % duty cycle, never 50 %. The fix keeps the counter and a flip-flop pos_q on the rising edge, pos_q high for (N − 1) / 2 periods, and adds a flip-flop on the falling edge that copies pos_q half a period later. Their OR is high for (N − 1) / 2 periods plus a half. The run measured 15 ns high in every 30 ns period on a 10 ns clock, and 14 ns when the input clock was high for only 40 %.

Warm-up (fresher): what does x & (x − 1) compute, and how does it test a word for one-hot?

Subtracting 1 from x clears its lowest set bit and sets every bit below it, so ANDing the result with x clears exactly the lowest set bit of x. The result is zero when x had at most one bit set, so (x & (x - 1)) == 0 is a check for zero-or-one-hot, SystemVerilog’s $onehot0, and adding x != 0 makes it exactly one-hot, $onehot. The exhaustive test of this lesson showed why the second term matters: without it, the check failed on one value of 256, the word 0.

Two follow-ups can come next. Choose the answer you would give; the feedback says what each one gets right or leaves out:

Checkpoint

Interview round: two follow-up questions

  1. Question 1 of 2 Interviewer: "The input clock is high for only 40 % of its period. Does your divider still give 50 %?"

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: No. The falling edge now comes at 40 % of the period, so the output is high for 1.4 of 3 periods, 46.7 %; an exact 50 % needs a clock from a PLL or a clock at twice the rate to count with

    The design gets its half cycle from the falling edge, so its duty cycle inherits the input's asymmetry. Say so, give the number, 46.7 % for a 40 % input, and offer a PLL or a doubled clock when the specification needs exactly 50 %.

  2. Question 2 of 2 Interviewer: "Now the grant vector may also be all zeros, when nobody requests. Change your check."
    Show the answer: Question 2 of 2

    Answer: Keep only (x & (x - 1)) == 0, at_most_one, which is $onehot0 and accepts the empty word

    A changed rule changes one term: at most one bit set is (x & (x - 1)) == 0, SystemVerilog's $onehot0. The exhaustive test's reference changes the same way, to ones <= 1, and the 256-value run then proves the new check.

Two harder questions follow, one about a divider whose first pulse is short on the board and one about a one-hot check that fails timing on a wide bus. Sketch your opening move for each before you read the first step under it:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    Your divide-by-5 clock with a 50 % duty cycle passed its testbench at N = 3, and on the board every period is right except the first output pulse after reset, which is short. What went wrong, and what do you change in the design and in the testbench?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A grant checker computes (g & (g - 1)) == 0 on a 64-bit grant vector, and the path fails timing at 1 GHz. Explain why, propose a structure that meets timing, and say how you would prove the new one equal to the old.

    Model answer, Pro. First step and follow-ups

Flashcards · 8 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. What are the five steps of a strong RTL whiteboard answer?

    Clarify, sketch, write, explain and verify. A follow-up question is a change of specification, which sends you back to the first step.

  2. Why does a divide-by-3 with a 50 % duty cycle need the falling edge?

    The output must be high for 1.5 input periods, and rising-edge logic changes only at whole periods, giving 33 % or 67 %. A flip-flop on the falling edge repeats pos_q half a period later, and their OR is high for N / 2.

  3. Why does the odd divider's reset load its counter with N - 1 instead of 0?

    Loading N - 1 makes the counter wrap at the first rising edge, so the first period after reset is a whole one. Reset to 0, the first pulse after reset was 15 ns instead of 25 at N = 5, a slip invisible at N = 3.

  4. What happens to the odd divider's output when the input clock is high for only 40 %?

    Its high time follows the input's falling edge: 1.4 periods in 3, 14 ns of 30, a 46.7 % duty cycle. An exact 50 % from an uneven input needs a PLL or a faster clock to count with.

  5. Why run a parameterised design's testbench at more than its default value?

    Some slips show only at other values: the counter reset to 0 passed at N = 3 and failed at N = 5.

  6. How does (x & (x - 1)) == 0 test a word?

    Subtracting 1 clears x's lowest set bit and sets the bits below it, so the AND clears exactly that bit; zero means at most one bit is set, $onehot0. Adding x != 0 gives exactly one-hot, $onehot.

  7. Why does a wide, fast bus need a tree for its one-hot check?

    x & (x - 1) ripples a borrow through every bit, a 64-gate path at 64 bits, while a tree of pairs that keeps "any bit set" and "two or more" had a 9-gate path at about the same 187 cells.

  8. Which Yosys commands give the numbers to say aloud about a design?

    stat counts the cells and flip-flops, and ltp -noff reports the longest path through gates between flip-flops and ports, the logic depth.

Key takeaways

  • A whiteboard answer runs in five steps, clarify, sketch, write, explain and verify, and a follow-up is a new specification that sends you back to the first.
  • Each problem family has one trap that a first answer falls into, and the trap is the follow-up to ask yourself.
  • An odd divider gets its half cycle from a flip-flop on the falling edge, so its output is high for N / 2 periods; with a 40 % input clock it was high for 14 ns of 30.
  • One self-checking testbench run at N = 3, 5 and 7 caught three slips, including a reset to 0 that only N = 5 shows.
  • x & (x − 1) checks for at most one set bit in one line, but its path was 64 gates deep at 64 bits against 9 for a tree of the same size, so a wide, fast bus needs the tree.
  • Verilator’s --lint-only -Wall reported six warnings for four slips in edge_count.sv and none for the fixed build.

Terms in this lesson

exhaustive test
A test that applies every possible input value and checks each result, practical while the input has few bits: 256 values for an 8-bit word, 65,536 for a 16-bit one.
logic depth
The number of gates on the longest path between flip-flops or ports. Each gate adds its delay, so a deeper path needs a slower clock; Yosys's ltp command reports it.
sequence detector
A state machine that reads a serial input one bit per clock cycle and raises its output when the latest bits match a pattern. An overlapping detector lets the last bits of one match start the next.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.