Verilog · lesson 11

Synthesizable RTL and Next Steps

What a synthesis tool actually does with a delay or an initial block, confirmed by real Yosys runs, a full coding checklist, and what to learn next.

10 min read · Free, no login

Returning to lesson 1's two readers

Lesson 1 opened this course with the idea that every piece of Verilog is read by two different programs with two different jobs โ€” a simulator, which executes the code faithfully regardless of whether it describes anything physical, and a synthesis tool, which only accepts a restricted subset and turns it into real gates. Nine lessons later, with real sequential logic, a working FSM, and a self-checking testbench behind us, this lesson draws that line precisely: exactly what gets built, what gets silently dropped, and what a designer needs to actively avoid.

What synthesis quietly ignores

A delay is the clearest example. It has real meaning to a simulator โ€” it changes exactly when a value becomes visible in time โ€” and zero meaning to real combinational hardware, whose propagation delay is a physical property of the manufactured gates, not something RTL can specify.

module delay_test (input wire a, input wire b, output wire y);
    assign #5 y = a & b;   // meaningful to a simulator, invisible to synthesis
endmodule

Running this through Yosys's synthesis flow produces a single $_AND_ cell and no warning at all about the missing delay โ€” it is not rejected, not flagged, simply dropped, because synthesis has nothing to do with it. This is worth sitting with for a moment: the exact same file simulates with a visible, five-time-unit delay on y, and synthesizes to hardware with no such delay encoded anywhere โ€” two different, equally "correct" readings of one file, exactly lesson 1's claim made concrete.

An initial block gets similar treatment when it targets a signal with no real storage behind it โ€” a plain combinational wire or an unregistered reg assigned only inside always @*. Simulating it, the initial value is a real, visible starting point before anything else has driven the signal; synthesizing it, there is no storage element for an "initial value" to live in, so the assignment is simply dropped from the netlist, confirmed above by the delay-test module synthesizing to exactly two wires and no additional cell for the initial statement. An initial block on an actual register (a signal assigned inside a clocked always block) is a different matter โ€” many FPGA synthesis flows do honor it, as the flip-flop's power-up value, but this is one of the places tools genuinely differ, and ASIC flows in particular often do not support it at all. The safe, portable pattern, and the one every sequential example in this course has used since lesson 7, is an explicit reset signal rather than relying on an initial value for anything that matters functionally.

What synthesis flatly rejects

Some constructs are not quietly dropped โ€” they are refused outright, because there is no physical hardware they could possibly correspond to. A real delay loop that waits for simulation time to pass with nothing else happening (#100; as a bare statement with no assignment), file I/O system tasks ($fopen, $fdisplay), and most of the simulation-control and display tasks this course has used constantly in testbenches ($display, $monitor, $finish, $dumpfile) all fall outside what any synthesis tool accepts as part of a design's real logic. This is by design, not a gap: every one of those constructs exists specifically to drive or observe a simulation, which is exactly lesson 2's distinction between a design module and a testbench module. The practical rule this course has followed throughout, without stating it explicitly until now, is that every design module โ€” anything meant to become real hardware โ€” avoids all of it, and every testbench module uses it freely, because a testbench is never synthesized at all.

A coding checklist, gathered from every lesson so far

Each of the following traces back to a specific, already-demonstrated lesson in this course, not a new rule introduced here for the first time.

  • Use ANSI-style port lists and named port connections for every instance (lesson 2).
  • Declare a literal's width and base explicitly; size a destination for the true result of an operation, not just its operands (lessons 3–4).
  • Use always @*, never a hand-written sensitivity list, for combinational logic (lesson 5).
  • Give every if an else and every case a default in combinational logic, unless a latch is genuinely intended (lesson 6).
  • Decide synchronous versus asynchronous reset deliberately, by what is and is not in a clocked block's sensitivity list, and check reset first in the branch order (lesson 7).
  • Use blocking assignment (=) for combinational logic and non-blocking assignment (<=) for sequential logic; never mix the two on one variable, and never let two blocks drive the same signal (lesson 8).
  • Split a state machine into three processes โ€” clocked state register, combinational next-state logic, combinational output logic (lesson 9).
  • Build every testbench to check itself, using !==/=== so an unexpected x is a guaranteed, visible failure (lesson 10).
  • Keep simulation-only constructs (delays on design logic, $display and friends, file I/O) out of anything meant to be synthesized.

Where this course leaves you, and what comes next

By this point, the course has covered everything needed to write a correct, synthesizable combinational or sequential module, verify it with a real self-checking testbench, and know precisely which parts of the code a synthesis tool will and will not act on. Lesson 12 extends the same discipline to code that needs to be reusable across different bit widths โ€” parameters and generate blocks โ€” and lesson 13 applies everything so far to a set of building blocks (a register file, a small ALU, a synchronous FIFO) that show up constantly in real interview questions and real RTL. Lesson 14 closes the course by moving from plain Verilog to SystemVerilog, which keeps everything taught here unchanged while adding a cleaner type system and some genuinely useful new constructs on top.

Beyond this course, the natural next step for most working digital designers is not more Verilog syntax โ€” it is static timing analysis, which is what decides whether a correctly functioning design will actually run at its intended clock frequency once placed and routed. The STA Library on this site picks up exactly there.

Interview questions

Give an example of Verilog that simulates correctly but has no synthesis meaning. A delay on a continuous assignment, such as assign #5 y = a & b; โ€” the delay changes exactly when the simulator makes the new value visible, but real combinational hardware's propagation delay comes from the manufactured gates, not from anything RTL can specify, so a synthesis tool silently drops it and builds a zero-specified-delay gate.

Why do $display and similar system tasks never appear in design, only testbench, code? They exist to drive or observe a simulation, not to describe hardware โ€” there is no synthesizable equivalent of "print this value," so a synthesis tool has nothing to do with them. The dividing line is exactly lesson 2's design-block-versus-stimulus-block distinction: synthesized code never contains them, testbench code uses them freely.

Does an initial block reliably set a register's power-up value in real hardware? It depends on the target and the tool โ€” many FPGA flows honor it as the flop's configured power-up state, while many ASIC flows do not support it at all. The portable, tool-independent way to guarantee a known starting state is an explicit reset signal, not an initial value.

Key takeaways

  • A delay on design logic simulates with real timing meaning and synthesizes to nothing at all โ€” confirmed here by a real Yosys run producing a plain gate with no warning about the dropped delay.
  • Simulation-only system tasks ($display, $finish, file I/O, and similar) belong only in testbench code, never in anything meant to be synthesized.
  • An initial value is reliable for simulation always, and for synthesis only on a real register, and even then only on some targets โ€” an explicit reset is the portable way to guarantee a starting state.
  • Every rule in this lesson's checklist was already demonstrated, with real simulated or synthesized evidence, in an earlier lesson of this course.