Digital Design / Module 11: Designing for silicon
60. What synthesis does to your code
Picture the gates your code becomes.
See it move
The idea
Synthesis turns your RTL into a netlist: real gates and flops selected from a technology library. Picture the hardware while writing code, because synthesis produces what you described, whether or not that is what you meant.
The steps
- Elaborate: read the RTL and resolve parameters and generate blocks.
- Translate: turn it into generic logic.
- Optimise: simplify logic, share resources, and choose structures.
- Map: select library cells that meet your timing constraints with least area and power.
Timing constraints drive every decision. The same RTL can map to a small, slow adder, or a large, fast one.
What doesn't synthesize
#delays are ignored, as timing comes from the cells.initialblocks are generally not supported for ASICs, though some FPGAs honour them.- Testbench features such as
$displayand file access are ignored or rejected.
Simulation and synthesis can disagree
Your RTL simulation and the resulting gates can behave differently. Common causes:
- Incomplete sensitivity lists: simulation doesn't react, but gates always do.
- :
if (x)takes the else path in simulation, while real gates hold a definite 0 or 1. - Blocking and non-blocking mix-ups.
- (
casezandxassignments) that the tool resolved in its own way.
Gate-level simulation and equivalence checking exist to catch these. Equivalence checking formally proves that the netlist matches the RTL.
Reading the reports
Always read the output: check for inferred latches, removed logic, and unconnected pins. Compare area by block, and check timing. A warning that a signal was optimised away can mean that you forgot to use it.
Common mistake
Writing clever RTL to save gates, in a way the tool would have done anyway, and making the code harder to read. Clear code usually gives equal or better results. Save your cleverness for architecture: that's where the real gains are.
Key ideas
- RTL maps to library cells using constraints.
- Initial blocks and delays usually aren't synthesized for ASICs.
- Simulation and gate-level results can mismatch.
Your exercise
Interview questions
coreWhy might simulation and synthesis disagree?
Incomplete sensitivity lists, X optimism, or simulation-only constructs.
basicWhat are the main steps a synthesis tool goes through to turn RTL into a netlist?
First it elaborates the design, reading the code and resolving parameters and generate blocks. Then it translates it into generic logic, gates and flops that are not tied to any library. Then it optimises, simplifying logic and sharing resources. Finally it maps the result onto real library cells, choosing cells that meet the timing constraints with the least area and power.
basicWhat hardware does each of these three lines become?
assign y = s ? a : b; always @(posedge clk) q1 <= d; always @(*) if (en) q2 = d;
The first is a 2-to-1 multiplexer selected by s. The second is a D flop, one per bit, clocked by clk. The third is a latch: q2 only gets a value when en is high, so the hardware must hold it when en is low, and that needs storage without a clock edge. Synthesis builds what the code describes, whether or not that is what was intended.
coreThis counter counts 0, 1, 2 in RTL simulation, but gate-level simulation shows X forever. Why?
module cnt (input clk, output reg [3:0] q); initial q = 4'd0; always @(posedge clk) q <= q + 4'd1; endmodule
For an ASIC, an initial block is not built into hardware, so the netlist has flops with no defined starting value. In gate-level simulation they start as X, and X plus 1 is still X, so the counter never recovers. In silicon it would start from whatever value the flops power up with. The fix is a reset that sets q to 0, which works the same way in RTL, gates and silicon.
coreThis block is meant to be an AND gate. In RTL simulation, setting a = 1 and then b = 1 leaves y at 0. What will the synthesized gate do, and why do they differ?
module and_bad (input a, b, output reg y); always @(a) y = a & b; endmodule
Synthesis ignores the sensitivity list and builds an AND gate, which reacts to both inputs, so y becomes 1 when b rises. The simulator only wakes this block when a changes, so the change on b is missed and y stays at its old value. The RTL and the chip now disagree, and every test run on the RTL tested behaviour the chip will not have. Use always @(*) so simulation and hardware match.
coreIn this mux, sel is X because it comes from a flop that was never reset. What does RTL simulation show for a = 1, b = 0, and what does real hardware do?
always @(*) begin if (sel) y = a; else y = b; end
An if with an unknown condition takes the else branch in simulation, so y shows a clean 0, the value of b. That hides the problem, which is called X-optimism. In silicon, sel is really a 0 or a 1, so y is either a or b, and nobody knows which. Gate-level simulation tends to show X here instead, and the real fix is to make sure sel is reset.
coreThe same 32-bit a + b can become a small adder in one design and a much larger one in another. Why?
Synthesis chooses the structure from the timing constraints. With a generous clock period, a small ripple-style adder can meet timing with the least area. With a tight period, the tool must use a faster structure, such as a prefix adder, which costs much more area and power. So the constraints, not just the RTL, decide what hardware you get.
advancedHow many adders does this code need? How might synthesis reduce that, and what does it cost?
always @(*) if (sel) y = a + b; else y = c + d;
Written directly, it is two adders followed by a mux. Because only one sum is used at a time, the tool can share one adder by putting muxes on its inputs: a or c on one side, b or d on the other. That saves an adder but adds a mux delay in front of it, so the path from sel to y gets longer. Whether the tool shares depends on the constraints and the tool's settings, so check the result in the reports.
advancedA synthesis report says a whole block of logic was removed, and the area is much smaller than expected. What do you check?
Synthesis removes logic that has no effect on any output, so first check whether the block's outputs actually reach a port or a used signal. A common cause is a missing connection, such as an output left unconnected or a typo in a port name. Constant inputs can also collapse logic, for example an enable tied low. A warning that a signal was optimised away often means you forgot to use it, so treat it as a possible bug rather than a free saving.