When Synthesis Goes Wrong: Debugging and Solutions
Synthesis is powerful, but it's not magic. The tool faithfully translates your RTL into gates, which means it will also faithfully translate your mistakes into hardware problems. Unlike software bugs that crash at runtime, hardware synthesis issues can create chips that behave unpredictably, fail timing closure, or simply don't work at all. The good news? Most synthesis problems fall into predictable categories with well-known solutions.
This module explores the most common synthesis issues you'll encounter: unintended latches, unmapped cells, combinational loops, unconnected ports, and constant tie cells. You'll learn to recognize these problems in synthesis reports and warnings, understand why they occur, and most importantly, fix them in your RTL. These debugging skills will save you countless hours and prevent expensive tape-out mistakes.
Latch Inference: The Accidental State
What Are Latches?
A latch is a level-sensitive storage element—it's transparent (passes input to output) when the enable is high, and holds its value when enable is low. Unlike flip-flops (edge-triggered), latches are level-triggered.
D-Latch Behavior
When EN = 1: Q follows D (transparent)
When EN = 0: Q holds previous value (latched)
Why Latches Are Usually Bad
Problems with Latches:
- Timing analysis nightmare: Level-sensitive timing is complex
- Race conditions: Glitches can cause incorrect latching
- Testability issues: Difficult to control and observe in testing
- Clock domain crossing: Complications with multiple clocks
- Technology mapping: Not all libraries have efficient latch cells
When Latches Are Intentional:
- Low-power designs (less switching than flip-flops)
- Asynchronous interfaces
- Specific architectural requirements
- But these are rare—99% of the time, latch inference is a bug
How Latches Get Inferred
Classic Mistake: Incomplete Combinational Logic
// BAD: Infers latch
always @(*) begin
if (select)
out = a;
// Missing else - what happens when select = 0?
// Synthesis infers latch to hold previous value
end
Why a Latch?
The always @(*) block describes combinational logic, but the output isn't defined for all input conditions. The synthesis tool assumes you want to retain the previous value, creating a latch.
More Examples of Latch Inference
Incomplete Case Statement
// BAD: Infers latch
always @(*) begin
case (opcode)
2'b00: result = a + b;
2'b01: result = a - b;
2'b10: result = a & b;
// Missing 2'b11 case - latch inferred for result
endcase
end
Conditional Assignment
// BAD: Infers latch
always @(*) begin
if (condition)
data_out = data_in;
// No else - latch inferred
end
Partially Assigned Multi-Bit Signal
// BAD: Infers latches for upper bits
always @(*) begin
case (sel)
2'b00: out[1:0] = 2'b00; // What about out[7:2]?
2'b01: out[3:0] = 4'b1111;
2'b10: out = 8'hFF;
2'b11: out[5:0] = 6'h3F;
endcase
end
Detecting Latch Inference
Yosys Warning
Warning: Latch inferred for signal `\out' on process `\my_module.$proc$design.v:15$1'.
Synthesis Statistics
yosys> stat
Number of cells:
$_DLATCH_P_ 1 # Latch detected!
$_DFF_P_ 8
Any cell with "LATCH" in the name indicates latch inference.
Visual Detection
yosys> show
Look for feedback loops with enable signals—characteristic of latches.
Fixing Latch Inference
Solution 1: Add Default Case
// GOOD: No latch
always @(*) begin
case (opcode)
2'b00: result = a + b;
2'b01: result = a - b;
2'b10: result = a & b;
2'b11: result = a ^ b; // Now all cases covered
endcase
end
Or use default:
// GOOD: Default handles all other cases
always @(*) begin
case (opcode)
2'b00: result = a + b;
2'b01: result = a - b;
2'b10: result = a & b;
default: result = 8'h00; // Explicit default
endcase
end
Solution 2: Add Else Clause
// GOOD: Complete specification
always @(*) begin
if (select)
out = a;
else
out = b; // Explicit else
end
Solution 3: Initialize Before Conditionals
// GOOD: Default value for all paths
always @(*) begin
out = 8'h00; // Default assignment
case (opcode)
2'b00: out = a + b;
2'b01: out = a - b;
2'b10: out = a & b;
// 2'b11 uses default value
endcase
end
This is the most robust approach—guarantees no latches.
Best Practice Pattern
always @(*) begin
// Default assignments first
result = 0;
valid = 0;
error = 0;
// Then conditional logic
case (state)
IDLE: begin
valid = 1;
end
ACTIVE: begin
result = data;
valid = 1;
end
ERROR: begin
error = 1;
end
// No default needed - all signals initialized
endcase
end
Unmapped Cells: Lost in Translation
What Are Unmapped Cells?
Unmapped cells are generic cells (like $add, $mux, $dff) that didn't get converted to technology library cells during technology mapping.
Example
yosys> stat
Number of cells:
$add 1 # Generic adder - NOT MAPPED!
AND2_X1 5 # Technology cell - mapped
DFF_X1 8 # Technology cell - mapped
The $add cell should have been mapped to technology-specific adder cells.
Why Cells Don't Map
Cause 1: Missing Library Cells
Your Liberty file doesn't contain cells needed for the operation:
assign result = a * b; // 8-bit multiply
If your library has no multiplier cells, the generic $mul cell can't map.
Cause 2: Unsupported Constructs
Some RTL constructs don't have direct technology mapping:
assign out = $signed(a) >>> 3; // Arithmetic shift
Not all libraries have arithmetic shift cells.
Cause 3: Technology Mapping Not Run
You forgot to run abc:
read_verilog design.v
hierarchy -check
proc
opt
# Oops - no 'abc' command!
write_verilog output.v
Cause 4: Mapping Failed
ABC couldn't find a suitable mapping (rare, but possible with complex logic).
Detecting Unmapped Cells
Check Cell Types
yosys> select t:$*
yosys> select -assert-none t:$*
If the assertion fails, you have unmapped cells.
Statistics Show Generic Cells
Number of cells:
$_DFF_P_ 8 # Generic flip-flop
$_AND_ 5 # Generic AND
Cell names starting with $ or $_ are generic (unmapped).
Fixing Unmapped Cells
Solution 1: Run Technology Mapping
read_liberty -lib cells.lib
abc -liberty cells.lib
Most common fix—just ensure abc runs.
Solution 2: Add Missing Cells to Library
If library truly lacks required cells:
- Use a more complete library
- Request library vendor to add cells
- Implement the function differently in RTL
Solution 3: Manual Mapping
For special cases, manually instantiate technology cells:
// Instead of:
// assign out = a * b;
// Use library multiplier directly:
MULT8_X1 u_mult (
.A(a),
.B(b),
.Y(out)
);
Solution 4: Change RTL
Rewrite to use available primitives:
// Instead of shift right arithmetic (>>>)
// Use arithmetic operations
assign out = $signed(a) / 8; // Division by power of 2
Solution 5: Use FPGA Synthesis
If targeting FPGA, use FPGA-specific synthesis:
synth_ice40 # For iCE40 FPGAs
synth_ecp5 # For ECP5 FPGAs
These map to FPGA primitives instead of ASIC standard cells.
Combinational Loops: The Feedback Problem
What Are Combinational Loops?
A combinational loop is a path through combinational logic that feeds back to itself—creating a cycle with no registers to break it.
Example
assign a = b & c;
assign b = a | d; // Loop! a depends on b, b depends on a
Why Combinational Loops Are Bad
Problems:
- Unstable behavior: Output oscillates or settles unpredictably
- Timing analysis impossible: No well-defined delay
- Simulation-hardware mismatch: Simulators may not catch the issue
- Synthesis tool confusion: May produce warnings or errors
Intentional Combinational Loops: Very rare—only in specialized circuits like:
- Arbiters
- Metastability resolvers
- Analog-style circuits
How Combinational Loops Occur
Mistake 1: Unintentional Feedback
// BAD: Combinational loop
always @(*) begin
temp = a & b;
out = temp | out; // out depends on itself!
end
Mistake 2: Circular Dependencies
// BAD: Module A uses Module B, Module B uses Module A output
module A (input x, output y);
assign y = x & b_out;
endmodule
module B (input p, output q);
assign q = p | a_out; // Circular!
endmodule
Mistake 3: Incorrect Latch Modeling
// BAD: Trying to model latch, created loop
assign q = enable ? d : q; // q depends on q combinationally
Detecting Combinational Loops
Yosys Warning
Warning: Found combinational loop in circuit:
wire $auto$simplify.cc:99:simplify$123
Visual Detection
yosys> show
Look for cycles in the graph that don't pass through flip-flops.
Check Command
yosys> check
Error: Found combinational loop
Fixing Combinational Loops
Solution 1: Add Registers
// GOOD: Break loop with register
always @(posedge clk) begin
temp <= a & b;
out <= temp | out_reg;
end
Solution 2: Restructure Logic
// BAD: Loop
always @(*) begin
temp = a & b;
out = temp | out;
end
// GOOD: No feedback
always @(*) begin
temp = a & b;
out = temp | c; // Use different signal
end
Solution 3: Use Proper Latch Syntax
// Instead of combinational feedback for latch
// Use proper level-sensitive always block
always @(enable or d) begin
if (enable)
q <= d;
// Synthesizes to latch correctly
end
Solution 4: Fix Module Interconnections
Break circular module dependencies:
// Redesign so data flows in one direction
// Use registers at module boundaries if needed
Unconnected Ports: The Floating Signal
What Are Unconnected Ports?
Unconnected ports are module inputs that aren't driven or outputs that aren't used.
Example
module counter (
input clk,
input rst,
input enable, // Never connected
output [7:0] count
);
// ...
endmodule
// Instantiation
counter u_cnt (
.clk(clk),
.rst(rst),
// .enable missing!
.count(count)
);
Why Unconnected Ports Are Problematic
Unconnected Inputs:
- May synthesize to undefined values (X in simulation)
- Could float in actual hardware (unpredictable behavior)
- Synthesis tool may tie to 0 or 1 (but which?)
Unconnected Outputs:
- Wasted logic (synthesis may not remove it)
- Confusion about design intent
- Could indicate a bug (forgot to use the signal?)
Detecting Unconnected Ports
Yosys Warnings
Warning: Ignoring unconnected input port 'enable' on cell 'u_cnt'
Warning: Ignoring unconnected output port 'overflow' on cell 'u_adder'
Elaboration Check
yosys> hierarchy -check
Reports unconnected ports.
Fixing Unconnected Ports
Solution 1: Connect the Port
// If port should be used, connect it
counter u_cnt (
.clk(clk),
.rst(rst),
.enable(1'b1), // Connect to constant or signal
.count(count)
);
Solution 2: Remove Unused Ports
// If truly unused, remove from module definition
module counter (
input clk,
input rst,
// enable removed
output [7:0] count
);
Solution 3: Explicit Unused Indication
// Document that port is intentionally unused
counter u_cnt (
.clk(clk),
.rst(rst),
.enable(), // Explicitly left unconnected
.count(count)
);
// Or in Verilog-2001
counter u_cnt (
.clk(clk),
.rst(rst),
// .enable not mentioned - implicitly unconnected
.count(count)
);
Solution 4: Default Port Values
// In module definition, provide defaults
module counter (
input clk,
input rst,
input enable = 1'b1, // Default value
output [7:0] count
);
Now if enable isn't connected, it defaults to 1.
Tie-High/Tie-Low Cells: Constant Connections
What Are Tie Cells?
Tie cells are special library cells that generate constant logic levels (0 or 1).
Common Names:
LOGIC0_X1,TIE_LO_X1(generates 0)LOGIC1_X1,TIE_HI_X1(generates 1)
Why Not Just Connect to Vdd/Gnd?
Problems with Direct Power/Ground Connections:
- ESD damage: Direct connection to power rail can cause ESD issues
- IR drop: Large fanout to many gates creates current spikes
- Reliability: Better to control constant sources explicitly
Benefits of Tie Cells:
- Controlled drive strength
- Proper buffering for fanout
- ESD protection
- Better for DFT (Design for Test)
When Tie Cells Appear
Scenario 1: Constant Inputs
module design (
input [7:0] data,
output [7:0] result
);
assign result = data & 8'hF0; // Lower bits always 0
endmodule
Synthesis may create tie-low cells for the constant 0 bits.
Scenario 2: Unused Enable Signals
DFF_EN u_ff (
.D(d),
.CK(clk),
.EN(1'b1), // Always enabled
.Q(q)
);
The constant 1 on EN uses a tie-high cell.
Scenario 3: Partially Used Buses
assign result[7:4] = 4'b0000; // Constant upper nibble
assign result[3:0] = data[3:0];
Detecting Tie Cells
Statistics
yosys> stat
Number of cells:
LOGIC0_X1 4
LOGIC1_X1 2
AND2_X1 12
DFF_X1 8
Check Constants
yosys> select t:LOGIC0_X1
yosys> select t:LOGIC1_X1
Managing Tie Cells
Normal Situation
A few tie cells are normal and expected:
LOGIC0_X1: 2-5 cells
LOGIC1_X1: 2-5 cells
Red Flag: Excessive Tie Cells
LOGIC0_X1: 100 cells
LOGIC1_X1: 50 cells
This suggests:
- Large constant values in RTL
- Many unused inputs
- Potential design issue
Solution 1: Review Constants in RTL
// BAD: Creating many tie cells
assign wide_bus = {16'h0000, data[15:0]};
// BETTER: Is the wide bus really needed?
// Consider redesigning to avoid constants
Solution 2: Optimize Away Constants
# During synthesis
opt_expr # Constant propagation
opt_clean # Remove unused logic
Solution 3: Use Fewer Tie Instances
Some libraries provide shared tie cells:
// One tie-low cell, buffered to drive many loads
TIELO_BUF u_tielow (
.Y(const_zero)
);
// Many gates use the buffered constant
Solution 4: Accept It
If your design genuinely needs constants, tie cells are the correct solution—don't try to eliminate them artificially.
Debugging Workflow for Synthesis Issues
Systematic Approach
Step 1: Check Synthesis Log
$ yosys -s synthesis.ys 2>&1 | tee synthesis.log
$ grep -i "warning\|error" synthesis.log
Step 2: Examine Statistics
yosys> stat
Look for:
- Generic cells (unmapped)
- Latch cells
- Excessive tie cells
Step 3: Run Checks
yosys> check
yosys> select -assert-none t:$* # No unmapped cells
Step 4: Visual Inspection
yosys> show
Look for:
- Feedback loops (combinational loops)
- Latch structures (unintended state)
- Dangling connections
Step 5: Fix RTL
Address issues in source Verilog, not synthesized netlist.
Step 6: Re-synthesize and Verify
$ yosys -s synthesis.ys
$ # Check that issues are resolved
Prevention Checklist
Before Synthesis:
- [ ] All combinational blocks have complete assignments
- [ ] All case statements have default cases
- [ ] All if statements have else clauses
- [ ] No circular module dependencies
- [ ] All instantiated ports connected or explicitly left unconnected
During Synthesis:
- [ ] Read synthesis warnings carefully
- [ ] Check statistics for generic cells
- [ ] Verify no latches (unless intentional)
- [ ] Run
checkcommand
After Synthesis:
- [ ] Review cell type distribution
- [ ] Verify no combinational loops
- [ ] Check for excessive tie cells
- [ ] Compare against expected results
Summary
Common synthesis issues have predictable causes and solutions:
- Latch inference: Incomplete combinational logic → Add defaults, else clauses, or full case coverage
- Unmapped cells: Missing technology mapping or library cells → Run
abc, use complete library, or change RTL - Combinational loops: Unintentional feedback paths → Add registers or restructure logic
- Unconnected ports: Missing connections → Connect ports, remove unused ports, or document intent
- Tie-high/tie-low cells: Constant generation → Normal in moderation; review if excessive
Key debugging skills you've developed:
- Recognizing warning patterns in synthesis logs
- Using
stat,check, andshowto diagnose issues - Tracing problems back to RTL source
- Applying systematic fixes
- Preventing issues with good coding practices
These debugging skills are invaluable throughout your career. Every designer encounters these issues, but experienced designers fix them quickly because they recognize the patterns. With practice, you'll spot potential problems in RTL before synthesis, write cleaner code, and spend less time debugging synthesis issues. The patterns you've learned here apply across all synthesis tools—commercial or open-source—making you a more effective hardware designer.