Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • Logic Synthesis Glossary
  • Logic Synthesis Cheatsheet
  • Synthesis Foundations14
    • Synthesis Flow Overview
    • Synthesizable RTL and Inference
      • Combinational Blocks Infer Logic
      • Priority Chains versus Parallel Selects
      • Latch Inference from Incomplete Assignment
      • Flops, Tri-States and Unrolled Loops
      • What Is Not Synthesizable
    • Reading Netlists and Reports
  • Logic Optimisation and Mapping20
  • Constraints and Timing15
  • Sequential, FSM and Memory19
  • Datapath Synthesis14
  • Area, Power and Test14
  • Verification, ECO and Handoff15
  • Flows and Debugging15
  • Interview and Judgment11
Loading lesson

←Synthesizable RTL and Inference
  1. Topics
  2. Logic Synthesis
  3. Synthesis Foundations
  4. Synthesizable RTL and Inference
  5. What Is Not Synthesizable
On this page

    Part 1 of 9, lesson 10 of 14Part 1 of 9/Synthesis Foundations/Lesson 10 of 14Intermediate

    What Is Not Synthesizable

    Delays, testbench blocks and real types have no gate-level meaning.

    Testbench idioms compile and simulate beautifully, then describe no hardware at all. The failure mode is silent: the tool ignores the construct, keeps the surrounding logic, and the chip disagrees with the testbench on day one.

    After this lesson you can: run each non-synthesizable construct through elaboration and state exactly what the tool kept, warned about, or ignored.

    The question

    Testbench idioms compile, simulate beautifully, and describe no hardware at all. The failure mode is silent: the tool ignores the offending construct, keeps the surrounding register, and the chip disagrees with the testbench on day one. Four constructs get a recorded run each next to the same 8-bit register, a delay, an initial block, a real parameter, and a display call, plus a clock-free oscillator on its own. Each runs through read_verilog and proc with stat and the JSON netlist saved. A note on honesty: each script runs to its PASS line, which proves elaboration continued past the construct, but warnings are not recorded, so the verdicts rest on the netlists. The delay run records 1 $dff and nothing else: that lone flop is the register, never the construct.

    Predict first Five constructs go through elaboration: a #5 delay, an initial block, a real parameter, a $display call, and a delay-driven oscillator with no clock. Which ones leave a cell behind?

    None of them. The recorded totals are 1 for the delay run, 1 for the initial run, 1 for the real run, 2 for the display run, and 0 for the oscillator run: the register flop every time it exists, plus a $print marker in the display run, and nothing anywhere else. A real variable is rejected at parse here, so the run uses the closest form that elaborates: a real parameter.

    Four runs, one pattern each, plus the oscillator

    Each of the four register scripts isolates one construct next to the same ordinary 8-bit register; the oscillator runs alone. Each elaborates, runs proc, and records stat plus the netlist. The assertions are deliberately narrow: one flop for the register, no $dlatch anywhere. Anything the construct added would show up as an extra cell in the recorded netlist, which is what makes the table checkable instead of rhetorical.

    yosys
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    read_verilog <<EOV
    module delay_nb (
      input wire clk,
      input wire [7:0] d,
      output reg [7:0] q
    );
      always @(posedge clk) begin
        q <= #5 d;
      end
    endmodule
    EOV
    hierarchy -check -top delay_nb
    proc
    tee -o stat_delay.txt stat
    write_json net_delay.json
    select -assert-count 1 t:$dff
    select -assert-none t:$dlatch
    log PASS: delay is ignored and the flop is still inferred
    yosys
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    read_verilog <<EOV
    module init_nb (
      input wire clk,
      input wire [7:0] d,
      output reg [7:0] q
    );
      initial q = 8'd0;
      always @(posedge clk) begin
        q <= d;
      end
    endmodule
    EOV
    hierarchy -check -top init_nb
    proc
    opt
    tee -o stat_init.txt stat
    write_json net_init.json
    select -assert-count 1 t:$dff
    select -assert-none t:$dlatch
    log PASS: initial block leaves one flop inferred with no latch
    yosys
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    read_verilog -sv <<EOV
    module real_nb (
      input wire clk,
      input wire [7:0] d,
      output reg [7:0] q
    );
      parameter real GAIN = 1.5;
      always @(posedge clk) begin
        if (GAIN > 1.0)
          q <= d;
        else
          q <= 8'd0;
      end
    endmodule
    EOV
    hierarchy -check -top real_nb
    proc
    tee -o stat_real.txt stat
    write_json net_real.json
    select -assert-count 1 t:$dff
    select -assert-none t:$dlatch
    log PASS: real parameter folds away and the flop is still inferred
    yosys
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    read_verilog <<EOV
    module disp_nb (
      input wire clk,
      input wire [7:0] d,
      output reg [7:0] q
    );
      always @(posedge clk) begin
        q <= d;
        $display("q=%h", q);
      end
    endmodule
    EOV
    hierarchy -check -top disp_nb
    proc
    tee -o stat_disp.txt stat
    write_json net_disp.json
    select -assert-count 1 t:$dff
    select -assert-none t:$dlatch
    log PASS: display call leaves the flop unchanged with no latch
    yosys
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    read_verilog <<EOV
    module osc_nb (
      output reg clk
    );
      initial clk = 0;
      always #5 clk = ~clk;
    endmodule
    EOV
    hierarchy -check -top osc_nb
    proc
    opt
    tee -o stat_osc.txt stat
    write_json net_osc.json
    select -assert-none t:$dlatch
    select -assert-none t:$dff
    log PASS: delay oscillator elaborates to no cells

    Construct against tool behaviour

    The table is the whole lesson in five rows. The behaviour column states only what the recorded netlist proves: cell counts from the JSON, plus the init-attribute reading where one exists.

    Construct

    Recorded netlist

    Tool behaviour

    #5 delay on the load

    1 cell: the register flop, nothing else

    Ignored: the flop is inferred exactly as without it.

    initial setting q

    1 cell: the register flop plus a power-up value

    Kept as metadata, not logic: init=00000000 on net q beside the register flop, no extra cell. FPGA flows honour it and ASIC flows do not.

    real parameter in elaboration

    1 cell: the register flop, parameter folded

    Acceptable: the parameter folds before mapping and leaves no cell.

    $display in the block

    2 cells: the register flop plus a $print marker

    Simulation-only: a $print marker rides along beside the logic.

    Delay oscillator, no clock

    no cells

    Vanishes: the toggle loop leaves no cell and the output dangles.

    Two rows deserve a second sentence. The delay row is harmless to synthesis and lethal to understanding: a reader sees sequencing where the netlist has simultaneity. The initial row is easy to misread as a reset: it runs once at time zero in simulation, while a reset runs on every asserted edge, and nothing on the chip performs the initial step unless the flow documents power-up values.

    Why each construct fails differently

    Each ignored construct fails for a different structural reason. A delay advances a simulation time wheel the synthesis model never builds. An initial block fires once at time zero while hardware sequencing needs repetition, so it can at most donate the recorded power-up value. The display call drives a simulator transcript the netlist has no port for. The real parameter succeeds for the opposite reason: it is an elaboration-time constant, so no real value reaches the cells, only the branch it selected.

    Treating an initial block as a maybe-reset is the expensive mistake to avoid: an async reset is checked against recovery and removal on deassertion, while a power-up value is a hope about the first cycle that no timing report covers. When in doubt, write the reset and delete the initial block.

    Recorded generic cell totals: 1 for the delay run, 1 for the initial run, 1 for the real run, 2 for the display run, and 0 for the oscillator run. No construct adds hardware.
    Flowchart from five construct groups to their fate. Delays, initial blocks and display calls yield nothing or metadata; real parameters fold away; only vectors and clocked blocks yield gates and flops.
    What survives elaboration: testbench idioms fall away and only vectors with bit-level meaning become gates and flops.

    Break it: a delay-based oscillator

    The testbench below is the classic delay idiom: a free-running toggle with no clock input, which oscillates in simulation and describes no hardware. It runs here in simulation only, while the osc_nb script above runs the same idiom through Yosys and records no cells: simulation toggles, elaboration leaves the output dangling.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    module tb_osc;
      reg clk;
      integer n;
      initial begin
        clk = 0;
        n = 0;
        #100;
        if (n >= 8) $display("PASS: delay oscillator toggled %0d times in simulation", n);
        else $display("FAIL: oscillator stalled at %0d edges", n);
        $finish;
      end
      always #5 begin
        clk = ~clk;
        n = n + 1;
      end
    endmodule

    Break it: a loop with no fixed bound

    An unbounded while loop is the construct this flow rejects outright. Run the listing below by hand: Yosys stops with the error "While loops are only allowed in constant functions" (hand-observed). The runner fails any block that prints an ERROR line, so this listing stays hand-run. The fixed-bound loop above unrolls because the tool can count its iterations; the while loop cannot be counted.

    yosys
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # not-run: Yosys rejects the loop with an ERROR line, which fails a recorded block, so run this listing by hand
    read_verilog <<EOV
    module wloop_nb (
      input wire clk,
      input wire go,
      input wire [7:0] d,
      output reg [7:0] q
    );
      always @(posedge clk) begin
        while (go) q <= d;
      end
    endmodule
    EOV
    hierarchy -check -top wloop_nb
    proc
    log PASS: unbounded while loop listing is rejected

    More constructs the netlist never sees

    The table below extends the lesson past what was recorded. Each construct is legal Verilog or SystemVerilog that this Yosys frontend rejects or cannot resolve; none can ship with a PASS line because the runner fails any block that prints an ERROR line. They are Yosys frontend behaviour, not recordings.

    Construct

    What this Yosys frontend does

    Status

    Unbounded while loop

    Rejected at elaboration: while loops are allowed only in constant functions.

    Yosys frontend behaviour: hand-run listing above.

    real variable declaration

    Rejected at parse: real x; is a syntax error in this frontend.

    Yosys frontend behaviour, confirmed by hand run.

    fork/join, wait, force/release

    Rejected at parse in this frontend.

    Yosys frontend behaviour, confirmed by hand run.

    File I/O ($fopen, $fdisplay)

    Rejected at elaboration: the function name cannot be resolved.

    Yosys frontend behaviour, confirmed by hand run.

    Common mistakes

    Code that simulates cleanly with delays and initial values can still describe no hardware. Re-read every testbench idiom before copying it into RTL, and distrust any design whose sequencing exists only as # delays: the netlist will show simultaneity where you imagined order.

    Which testbench idioms never reach the netlist, and what does the tool keep instead?

    Weak answer: delays and initial blocks mostly work, and reals work with care.

    Strong answer: delays leave nothing beyond the register flop, as the recorded 1 cell show; initial is a power-up value at most; a real parameter folds at elaboration; $display is simulation-only, as the recorded 2 cells show. Anything without bits has no gates.

    Follow-up: the chip powers up wrong although simulation was clean. Answer: an initial block set the state in simulation while the silicon has no such step; add a real reset and re-verify from the reset state.

    Check yourself A #10 delay sits in RTL. What hardware does it become?

    None. The recorded delay run totals 1 cell: the delay advances simulation time only and never reaches the netlist.

    Check yourself An initial block sets a counter at time zero. Why might the chip disagree?

    The block is testbench code. The recorded initial run totals 1 cell with init=00000000 on q, so nothing on the chip performs that step unless the flow honours power-up values; use reset logic instead.

    • Delays advance simulation time only and leave no cell behind.

    • initial records an init attribute on the net: honoured by FPGA flows, ignored by ASIC flows.

    • A real parameter is the acceptable case: it folds at elaboration, while real variables, unbounded loops and file I/O are rejected.

    Rule of thumb: if it has no bits, it has no gates.

    How this was checked

    Every row of the table comes from Yosys 0.70+post runs recorded by the course checker: delay, initial, display and oscillator variants run read_verilog with proc, and the real variant runs read_verilog -sv with a real parameter steering an elaboration-time branch; stat plus write_json are recorded per variant. Each of the four register runs records the register flop and nothing else for its construct, and the recorded totals carry the numbers. A real variable declaration does not parse in this Yosys frontend, so the parameter is the runnable representative, and an unbounded while loop ships as a hand-run listing because its ERROR line fails a recorded block. The initial verdict rests on the init attribute proc_init records on the net, read from the recorded JSON. Anything about warnings is stated only as observed continuation to PASS, and the extra constructs are marked as Yosys frontend behaviour, not recordings.

    • Interview: run a mock interview on this topic

    Related topics

    • Flops, Tri-States and Unrolled Loops

    • Simulation Synthesis Mismatch

    • Synthesis Flow Overview

    Previous

    12 / 139

    Next: Reading Netlists and Reports

    Up next

    Up nextReading Netlists and Reports

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Combinational Blocks Infer Logic
    • Priority Chains versus Parallel Selects
    • Latch Inference from Incomplete Assignment