← RTL Design Interviews

Interview Question · Code Reading

A Blocking Temporary Inside Always_ff

L1 JuniorL2 Mid-Levelblocking nonblockingsequential logicsynthesis

The Question

The interviewer asks

This block mixes = and <=. Is it legal? How many flops does it build?

systemverilog
module avg2 (input logic clk, input logic [7:0] a, b, output logic [7:0] avg);
  logic [8:0] sum;
  always_ff @(posedge clk) begin
    sum  = a + b;        // blocking: a temporary, used only below
    avg <= sum[8:1];
  end
endmodule
Reveal the Model Answer

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.

What the Averaging Block BuildsArrows carry values inside one clock cycle. Only avg is a register.
What the averaging block buildsArrows carry values inside one clock cycle. Only `avg` is a register. component a[7:0] input component b[7:0] input component Adder sum = a + b, 9 bits, noflop component sum[8:1] drops bit 0: divide by2 register avg 8 flops absum (a wire)D input
  1. A wire

    sum is written with = before it is read, on every path. Nothing has to remember it, so it is a wire.

  2. The flops

    avg <= sum[8:1] is the only value kept from edge to edge: 8 flops.

Read the diagram as text
  • a[7:0]: input
  • b[7:0]: input
  • Adder: sum = a + b, 9 bits, no flop
  • sum[8:1]: drops bit 0: divide by 2
  • avg: 8 flops
  • a[7:0] → Adder: a
  • b[7:0] → Adder: b
  • Adder → sum[8:1]: sum (a wire)
  • sum[8:1] → avg: D input

The pinned Yosys flow reports 8 flops for this block, all for avg.

The snippet with a bench, and the flop count on the pinned Yosys flow.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: one edge after a=10, b=20: avg=15

Self-Check

Tick each key point you covered in your answer.

What Interviewers Listen For (Junior)

A good junior answer explains why sum is only a wire: every path writes it before reading it. It counts 8 flops.

Cross-Questions

Where the interviewer goes next. Open a node to practise that follow-up.

WhyWhat IfBreak ItCompare

  1. A Blocking Temporary Inside Always_ff
  2. WhyWhy Blocking Is Allowed in Always_ffL2 Mid-Level

    If blocking writes cause so many bugs in clocked blocks, why does the language allow them in always_ff?

    Reveal the Model Answer

    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.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "always_ff turns = into <= for you."

    It changes nothing about how assignments behave. A blocking write in always_ff still updates at once, as the shift-register question shows.

    Source IEEE 1800-2017 §9.2.2.4

  3. What IfAnother Block Reads the TemporaryL2 Mid-Level

    What if a second always_ff block reads sum at the same clock edge?

    Reveal the Model Answer

    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.

    Self-Check

    Tick each key point you covered in your answer.

    Missed Corner

    "It is fine because sum is just a wire in the netlist."

    The netlist is not the problem; the simulation is. Your RTL tests could pass with one order and hide a bug the gates do not have.

    Source Sutherland, RTL Modeling with SystemVerilog (2017) ch. 8.1.4.3 Using blocking assignments for temporary variables (note)

  4. What IfWhen Two Simulators Agree on a RaceL2 Mid-LevelL3 Senior

    What if Icarus and Verilator give the same result for that race? Is the code safe then?

    Reveal the Model Answer

    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.

    Self-Check

    Tick each key point you covered in your answer.

    Tool as Truth

    "Both simulators agree, so the order is fixed."

    The standard leaves the order open. Tools agree because of how they happen to be built, and any edit can change that.

    Source IEEE 1800-2017 §4.7

  5. CompareAn Inline Temporary vs a Separate Combinational BlockL2 Mid-Level

    Compare the inline temporary with computing sum in its own always_comb block.

    Reveal the Model Answer

    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.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "The separate block adds a register stage."

    always_comb describes logic with no memory. The sum still reaches the flop in the same cycle.

    Source Sutherland, RTL Modeling with SystemVerilog (2017) ch. 7.2 The always and always_comb procedures

  6. Break ItA Temporary Written on Only One PathL2 Mid-LevelL3 Senior

    How would you break the temporary? Suppose sum is written only under if (en). What does this print, and what does synthesis build?

    systemverilog
    module avg2e (input logic clk, en, input logic [7:0] a, b, output logic [7:0] avg);
      logic [8:0] sum;
      always_ff @(posedge clk) begin
        if (en) sum = a + b;      // written only when en is 1
        avg <= sum[8:1];
      end
    endmodule
    module tb;
      logic clk = 0, en = 1; logic [7:0] a = 10, b = 20, avg;
      avg2e dut (.clk(clk), .en(en), .a(a), .b(b), .avg(avg));
      always #5 clk = ~clk;
      initial begin
        @(posedge clk); #1 en = 0; a = 100; b = 100;
        @(posedge clk); #1;
        $display("en=0, a=b=100: avg=%0d", avg);
        $finish;
      end
    endmodule
    
    Reveal the Model Answer

    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.

    RUNIcarus Verilog · in your browser
    Edit the code, then run it
    Expected: en=0, a=b=100: avg=15

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "A blocking temporary is always a wire."

    Only when every path writes it before reading it. Here the en = 0 path reads the old value, which needs storage.

    Source Measured: the run on this question, Icarus Verilog 12

What People Get Wrong

Common wrong answers and why they fail. Run It shows the wrong belief failing on a real simulator.

Misconception

"The order of lines inside always_ff does not matter."

It matters for blocking writes. If sum is read before it is written, the read sees last cycle's value, so sum becomes a register. The twin shows the extra flops.

Run It

The two lines swapped, then the snippet as given.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: one edge after a=10, b=20: avg=x FAIL: line order inside always_ff matters; reading sum first made it a register
See the Fix
module avg2 (input logic clk, input logic [7:0] a, b, output logic [7:0] avg);
  logic [8:0] sum;
  always_ff @(posedge clk) begin
    sum  = a + b;        // blocking: a temporary, used only below
    avg <= sum[8:1];
  end
endmodule
module tb;
  logic clk = 0; logic [7:0] a = 0, b = 0, avg;
  avg2 dut (.clk(clk), .a(a), .b(b), .avg(avg));
  always #5 clk = ~clk;
  initial begin
    a = 8'd10; b = 8'd20; @(posedge clk); #1;
    $display("one edge after a=10, b=20: avg=%0d", avg);
    if (avg === 8'd15) $display("PASS: sum is written before it is read; no extra register");
    $finish;
  end
endmodule

Prints: one edge after a=10, b=20: avg=15 PASS: sum is written before it is read; no extra register

Misconception

"Mixing = and <= in one block is always illegal."

The language allows it. A blocking temporary that is written before it is read, and used only in that block, simulates and synthesizes correctly.

Source Sutherland, RTL Modeling with SystemVerilog (2017) ch. 8.1.4.3 Using blocking assignments for temporary variables

Half-Truth

"Every variable assigned in always_ff becomes a flop."

Only variables whose value must survive to the next edge. The run counts 8 flops for an 8-bit avg and a 9-bit sum.

Source Measured: the run on this question, Yosys 0.70+290 (OSS CAD Suite 2026-11-02), slang front end, synth -flatten with ABC, ltp -noff

Learn It, Then Prove It