GLS · Chapter 1 · RTL vs Gate-Level Simulation
What Synthesis Changes — and Why It Matters
Synthesis does not copy your RTL into gates. It transforms your design: it optimises logic through Boolean simplification, constant propagation, and dead-logic removal, infers flip-flops and maps them to real library cells, shares resources, and restructures combinational logic. The guarantee that matters for gate-level simulation is precise. The netlist computes the same outputs as the RTL for defined zero-and-one inputs, but it does not promise the same structure, the same unknown behaviour, or the same result on inputs you left unspecified. So the optimised structure can propagate unknowns along different paths, and any don't-care you specified gets resolved to whatever is cheapest, which may differ from what RTL simulation showed. This lesson maps the transformations and breaks the classic divergence where a don't-care that RTL simulated as unknown is resolved by the gates to a constant, making RTL and gate-level simulation disagree.
Foundation12 min readGLSSynthesisOptimisationDon't-CareStructure
Chapter 1 · Section 1.2 · RTL vs Gate-Level Simulation
Project thread — 1.1 named the structure axis; this lesson opens it. The same D flip-flop's always_ff becomes a real cell through these transformations, and a don't-care here foreshadows the X-thinking from 0.2.
1. Why Should I Learn This?
If you think synthesis 'just turns RTL into gates,' every optimisation-driven divergence in GLS looks like a bug. In truth synthesis transforms your design under a precise guarantee — equivalent for defined inputs — and everything outside that guarantee (structure, X-behaviour, unspecified inputs) is fair game for the tool to change. Knowing exactly what synthesis is allowed to change, and why 'equivalent for defined inputs' is the whole promise, is what lets you predict which GLS divergences are the tool doing its job and which reveal a coding problem — like relying on a don't-care.
This lesson details the mechanism behind 1.1's structure axis and sets up 1.3 (constructs that vanish are the extreme case of transformation) and 1.4 (two-state simulation and X).
2. Real Silicon Story — the case default that changed value in the gates
A team's block passes RTL simulation. In GLS, one output takes a different value than RTL on a particular input combination — a clean 0 in the gates where RTL showed X. It looks like synthesis computed the wrong thing, and a 'synthesis functional bug' is escalated.
Synthesis had computed exactly what it was told it was free to. The RTL had a case statement with an uncovered input combination and a default that produced X (a don't-care): 'I do not care what happens here.' RTL simulation faithfully propagated that X. But synthesis read the don't-care as a free choice and optimised the logic to whatever was smallest — which, for that input, happened to be a constant 0. So on that input the gate output is 0 and the RTL output is X: they disagree, because the designer declared the value irrelevant and the two tools resolved the freedom differently. This was not a synthesis defect — it was a design issue: the block was reaching an input combination the designer had marked don't-care but that actually occurred, so the 'don't-care' was a real case whose behaviour was now tool-dependent. The fix was to specify the intended behaviour, not to 'correct synthesis.' The post-mortem lesson: synthesis is functionally equivalent to RTL for defined inputs, but it exploits don't-cares — behaviour you left unspecified is a free choice it optimises, so the gate result on a don't-care input can differ from the RTL simulation's X; when RTL and GLS disagree on such an input, the bug is usually the reliance on a don't-care, not synthesis.
3. Concept — what synthesis transforms, and the exact equivalence
Synthesis applies several transformations, all preserving function for defined inputs:
- Logic optimisation. Boolean simplification, constant propagation (a signal driven by a constant is folded away), and dead-logic removal (logic that cannot affect any output is deleted). Internal RTL signals disappear.
- Flip-flop inference and cell mapping.
always_ffbecomes a real library flip-flop cell (DFFRX1, etc.) chosen for timing/area/drive. - Resource sharing. One physical operator (an adder, a mux) serves multiple mutually-exclusive RTL uses.
- Restructuring / retiming. Combinational cones are re-shaped (and sometimes registers moved across logic) to meet timing.
- The exact equivalence. The guarantee is equivalent outputs for defined (0/1) inputs — not the same structure, not the same X-behaviour, not the same result on unspecified inputs.
The two GLS-relevant consequences:
- Structure changed → X and timing behave differently. The optimised netlist may propagate an unknown along a different path (more/less X-pessimistic), or lose a redundancy the RTL sim leaned on to define a value.
- Don't-cares are exploited. Unspecified behaviour (
default: x, uncoveredcase,full_case) is a free choice; synthesis picks the cheapest value, which can differ from RTL sim's X — so RTL and GLS disagree on that input.
Here is the pipeline from an RTL construct to its GLS implication:
4. Mental Model — synthesis is a translator bound only to the meaning you pinned down
5. Working Example — a don't-care that RTL sims as X and gates resolve to a constant
A small decoder with an uncovered input. Watch RTL and GLS disagree on it.
// RTL (SystemVerilog): a case that does NOT cover all inputs; the default is a DON'T-CARE (x).
module dec (input logic [1:0] sel, output logic y);
always_comb begin
case (sel)
2'b00: y = 1'b0;
2'b01: y = 1'b1;
2'b10: y = 1'b1;
// 2'b11 NOT covered -> default don't-care
default: y = 1'bx; // "I don't care what y is for sel=2'b11"
endcase
end
endmoduleThe same decoder in Verilog and VHDL — all three leave sel=2'b11 unspecified, so RTL sim shows X there while synthesis is free to resolve it to a constant:
// RTL (Verilog-2001): same decoder; sel=2'b11 is a don't-care.
module dec (input [1:0] sel, output reg y);
always @(*)
case (sel)
2'b00: y = 1'b0;
2'b01: y = 1'b1;
2'b10: y = 1'b1;
default: y = 1'bx; // don't-care -> synthesis picks the cheapest value
endcase
endmodule-- RTL (VHDL): same decoder; 'others' is the don't-care for "11" (std_logic 'X').
process(sel)
begin
case sel is
when "00" => y <= '0';
when "01" => y <= '1';
when "10" => y <= '1';
when others => y <= 'X'; -- don't-care for "11": gates may resolve to a constant
end case;
end process;// NETLIST (representative): synthesis is FREE on the don't-care -> optimises y for sel=11 to a CONSTANT.
// For sel=11, the cheapest logic happens to drive y = 0 (a real, defined gate value).
module dec_netlist (input [1:0] sel, output y);
// ... optimised gates: y = sel[0] | sel[1]&~sel[0] (illustrative) -> for sel=11, y = 1? or 0?
OAI21X1 U0 (.A(sel[0]), .B(sel[1]), .C(1'b0), .Y(y)); // representative optimised cell
endmoduleThe RTL and GLS logs disagree on the uncovered input:
# RTL SIM: the default don't-care propagates X on sel=11.
sel=00 y=0 | sel=01 y=1 | sel=10 y=1 | sel=11 y=x <- RTL shows X on the uncovered input
# GLS: synthesis resolved the don't-care to a CONSTANT (defined) value on sel=11.
sel=00 y=0 | sel=01 y=1 | sel=10 y=1 | sel=11 y=0 <- gates show a DEFINED value (they DISAGREE with RTL)The disagreement is only on sel=11 — the input you marked don't-care. Synthesis did not miscompute; it used the freedom you gave it. The real question is: does sel=11 ever actually occur? If yes, you have a coding bug (relying on a don't-care that happens), and the fix is to specify the value — not to 'correct synthesis.'
6. Debugging Session — RTL and GLS disagree on one input
A gate output disagrees with RTL simulation on exactly one input combination — a don't-care the RTL simmed as X and synthesis optimised to a constant, exposing reliance on unspecified behaviour
EQUIVALENCE IS FOR DEFINED INPUTS; DON'T-CARES ARE FREERTL simulation and GLS agree on almost every input, but on one combination the gate output is a defined value (say 0) while RTL showed X. It looks like synthesis computed the wrong result on that input — a 'synthesis functional bug.'
The input in question is a don't-care: the RTL left it unspecified (an uncovered case, a default: x, or a full_case pragma), so RTL simulation propagated X, while synthesis — which reads a don't-care as a free choice — optimised the logic to whatever was cheapest, resolving that input to a constant. So the two disagree precisely because the behaviour was unspecified: synthesis's equivalence guarantee is outputs match for defined inputs, and this input was not defined. Synthesis did not miscompute — it used the freedom the code gave it. The real problem surfaces only if that 'don't-care' input actually occurs in operation: then the design's behaviour there is tool-dependent (this synthesis run picked 0; a different run, tool, or constraint could pick 1), which is a latent, unpredictable bug — and it is a coding issue (relying on unspecified behaviour), not a synthesis defect. The tell is that the disagreement is confined to an input the code left don't-care, and that GLS shows defined where RTL shows X (the opposite of a normal X-escape).
Decide whether the don't-care input can occur. If it cannot (truly unreachable), the divergence is harmless and expected — document it. If it can, specify the intended behaviour in the RTL (cover the case, set a real default) so the value is defined and deterministic across tools, and re-synthesise/re-run so RTL and GLS agree. The lesson: synthesis is equivalent to RTL only for defined inputs; it exploits don't-cares as free choices, so a gate result on an unspecified input can differ from RTL sim's X — a GLS disagreement confined to a don't-care input is a coding bug (relying on unspecified behaviour) exposed by the gates, fixed by specifying the value, not by 'correcting synthesis.' More broadly, remember the guarantee: expect the netlist to differ in structure, X-behaviour, and unspecified inputs — and only defined-input functional mismatches point at a genuine synthesis/constraint problem.
7. Common Mistakes
- Thinking synthesis 'just copies RTL to gates.' It transforms (optimise, map, share, restructure) — equivalent only for defined inputs, not the same structure.
- Reading a don't-care disagreement as a synthesis bug. Unspecified behaviour is a free choice; the gate result differing from RTL sim's X is expected — the bug is relying on the don't-care.
- Relying on unspecified behaviour. An uncovered
case,default: x, orfull_caseon an input that occurs makes behaviour tool-dependent — specify it. - Expecting the same internal signals/X-paths as RTL. Optimisation removes redundancy and renames — X can propagate along a different path in the netlist.
- Assuming 'equivalent' means 'identical waveforms.' Equivalence is defined-input outputs, not identical structure, timing, or X-behaviour.
8. Industry Best Practices
- Hold the precise guarantee in mind. Synthesis preserves defined-input function — not structure, X-behaviour, or unspecified-input results.
- Specify every input you can reach. Cover
casestatements, avoidfull_case/don't-cares on reachable inputs, so behaviour is deterministic across tools. - Triage a GLS disagreement by input type. Defined-input mismatch -> real synthesis/constraint issue; don't-care-input mismatch -> coding reliance on unspecified behaviour.
- Expect optimised structure and different X-paths. Do not chase renamed signals or relocated X as bugs.
- Treat a resolved don't-care as a latent bug if the input occurs. Its value is tool-dependent — pin it down.
Senior Engineer Thinking
- Beginner: "The gates give a different value than RTL — synthesis computed it wrong."
- Senior: "On which input? If it is one I left don't-care, synthesis was free to pick — and the real bug is that I relied on unspecified behaviour. Does that input actually occur? If so, I specify it; I do not 'fix synthesis.'"
The senior knows the equivalence is defined-input outputs only, so a disagreement routes to which input — defined (real issue) or don't-care (coding issue).
Silicon Impact
If a resolved don't-care escapes, the danger is tool-dependence: the input you marked irrelevant actually occurs, and its behaviour was fixed by this synthesis run to a value RTL never simulated. A re-synthesis, an ECO, a library swap, or a different corner can flip that value — so the design works today and fails after an unrelated change, an insidious, hard-to-bisect failure. In the field it presents as behaviour that depends on a specific silicon revision or build, driving respins and field returns for a value that was never actually specified. Specifying reachable inputs turns a tool-dependent time-bomb into deterministic behaviour.
Engineering Checklist
- Held the guarantee: synthesis is equivalent for defined inputs only.
- Covered every reachable
caseinput; avoidedfull_case/don't-cares on reachable inputs. - Triaged each RTL-vs-GLS disagreement by input: defined (real) vs don't-care (coding).
- Specified any 'don't-care' input that can actually occur.
- Did not chase optimised structure or relocated X as synthesis bugs.
Try Yourself
- Simulate the decoder RTL four-state and drive
sel = 2'b11(the uncovered input). - Observe: RTL sim shows
y = Xonsel=11— the don't-care. - Change: cover the case — add
2'b11: y = 1'b0;(or1'b1) — and re-simulate. - Expect: now RTL and the gates agree deterministically on
sel=11; while it was don't-care, the value was tool-dependent. The lesson: specify every input you can actually reach.
A standard four-state simulator is enough — no paid tool needed; commercial synthesis tools resolve the don't-care the same way, they are just industry context here.
Interview Perspective
- Weak: "The gates give a different value than RTL, so synthesis computed it wrong."
- Good: "Synthesis is equivalent for defined inputs; on a don't-care it is free to choose, so a disagreement there is expected."
- Senior: "I check which input disagrees. A defined-input mismatch is a real synthesis/constraint issue; a don't-care mismatch is reliance on unspecified behaviour — I specify the input if it can occur, I don't 'fix synthesis.'"
Quick Revision
Synthesis transforms, it does not copy. It optimises, maps to cells, shares, restructures — equivalent for defined inputs only. Structure, X-behaviour, and unspecified inputs are free to change. A GLS disagreement on a don't-care input is a coding bug (specify it), not a synthesis bug. Next: 1.3 covers the extreme case — constructs that don't just change but vanish.
9. Interview / Review Questions
10. Key Takeaways
- Synthesis transforms RTL, it does not copy it — it optimises (simplify, constant-propagate, remove dead logic), maps
always_ffto real cells, shares resources, and restructures. - The exact equivalence guarantee is same outputs for defined (0/1) inputs — not the same structure, X-behaviour, or result on unspecified inputs.
- Two GLS consequences: the optimised structure changes X-propagation and timing (relocated / different X, real delays), and don't-cares are exploited — unspecified behaviour is optimised to the cheapest value.
- A GLS disagreement on a don't-care input (gates show a defined value where RTL shows X) is expected, and is a coding bug (relying on unspecified behaviour) only if that input can occur — fixed by specifying the value, not correcting synthesis.
- Only a functional mismatch on a defined input points at a genuine synthesis/constraint problem — everything else (structure, X-paths, don't-cares) is the tool exercising the freedom you gave it. Next: 1.3 — the constructs that vanish entirely.