Timing Series: Part 1 of 6

Previous: Your FPGA Lives a Lifetime While You Blink


Signoff Gates: Build Must Fail If…#

Before reading further, know this: your build should fail if any of these checks fail.

Gate 1: Unconstrained endpoints must be zero

check_timing -verbose
report_exceptions -verbose

Fail if: Any constraint-related issue about clocks, unconstrained endpoints, or missing I/O delays. Also fail if any exception matches zero objects (silent no-op) or hits an unexpectedly large set.

Action: Fix constraints until remaining items are explicitly intentional and documented.

Gate 2: All expected clocks must exist with correct periods

report_clocks

Fail if: Any expected clock is missing, or any period is wrong.

Action: First verify source create_clock binds correctly. Only add create_generated_clock if the tool truly cannot derive it.

Gate 3: Clock relationships must match reality

report_clock_interaction

Fail if:

  • A clock pair is “Timed” but is actually asynchronous
  • A clock pair is “Async” but is actually related (same source)
  • Any “unclocked” or “no clock” endpoints exist

Action: If you cannot prove clocks are related (same source with known relationship), treat as async and implement CDC. Do not use constraints to guess - decide architecture first, constraints second.

If you ship with any of these gates failing, “Timing Met” is fiction.


The Lie You Told Yourself#

Your design passed timing. Zero violations. Green across the board. You shipped it.

Three weeks later, it fails in the field. Intermittent. Unreproducible in the lab.

The postmortem takes two weeks. The answer is one line in your XDC file that isn’t there.

You constrained the 100 MHz input clock. But the PLL output - the 156.25 MHz clock that actually feeds your datapath - has no constraint. The tools timed a fraction of your design.

(You can get the same failure from: missing I/O delays, clock defined on the wrong object, or over-broad exceptions.)


Constraints Are the Contract#

Constraints define the environment. Clocks, I/O timing, domain relationships.

Timing analysis verifies your design against that environment.

Missing or wrong constraints? The tool’s answer is meaningless. Prove the contract is enforced: check_timing, report_clocks, report_clock_interaction.


create_clock: Making the Invisible Visible#

A clock without a constraint is unconstrained. Paths clocked by that signal have no setup/hold checks, and optimization isn’t driven by a real deadline.

create_clock -period 6.4 -name clk_156 [get_ports clk_in]

What happens when it’s missing: check_timing warns about unconstrained endpoints - but only if you treat those warnings as errors.

What happens when it’s wrong: You say 10 ns, but the real clock is 6.4 ns. Hardware fails at the real frequency.


Generated Clocks: Where Designs Actually Break#

MMCM/PLL Outputs#

In Vivado, if you use the Clocking Wizard IP, create_generated_clock constraints are automatically written. Auto-derivation depends on the tool being able to trace from a properly constrained source clock through the clocking resource. Still verify with report_clocks; don’t assume.

If report_clocks does not show what you expect, treat it as a hard error.

If report_clocks already shows the generated clock, do not re-declare it. Engineers sometimes redeclare with the wrong source pin or divide ratio, and now STA is lying while still “green.”

Fabric Clock Dividers - Don’t Do This#

The rule is not “no divided clocks” - it’s “no LUT-generated clocks for real logic.” Divided clocks from dedicated clocking resources (BUFGCE_DIV, MMCM) are fine. Divided clocks from random LUT logic are the problem: high skew, not on a clock network.

For performance-critical logic, use clock enables (CE) instead.


Clock Relationships: Setting Up CDC#

set_clock_groups -asynchronous#

set_clock_groups -asynchronous -group {clk_156} -group {clk_125}

This deletes paths from timing analysis in both directions. It can also hide accidental combinational paths between domains.

Do CDC first, then constrain it. Don’t use constraints to make timing problems disappear.

Preferred pattern: Keep clocks separate for timing, but only false-path into the first synchronizer stage. Use set_clock_groups only when you truly want the tools to ignore all crossings.

The CDC Minimum Bar:

Crossing TypeRequired Structure
Single-bit level2-flop synchronizer with ASYNC_REG
PulsesToggle or req/ack handshake
Multi-bit busesAsync FIFO or gray-coded pointers
ResetsSynchronize deassertion per domain

Why ASYNC_REG matters: This RTL attribute tells the tool these flops are a synchronizer, so it avoids retiming/merging and tries to keep stages physically close. ASYNC_REG requires two flops in series. Verify placement stayed intact.


I/O Delays: Timing at the Chip Boundary#

I/O constraints model the physical PCB. Without them, timing reports are internal-only.

Define Your Reference Point or You’re Lying to STA#

Before writing I/O constraints, decide what your -clock object represents:

Option A (common for outputs): -clock is the capture clock edge at the external device. Use a virtual clock if that clock doesn’t enter the FPGA. Dclk and Ddata must be defined relative to that device.

Option B: -clock is the clock edge at the FPGA pin. Then you must explicitly include clock flight time from FPGA to device in your calculations.

Pick one. Don’t mix them within a single interface calculation. Put “from → to” in your notes.

Input Delay Math#

Reference: Clock and data arriving at the FPGA pins.

                Clock source
              ┌──────┴──────┐
              │             │
    Dclk (source→FPGA)    External Device
              │             │
              ▼             │ Tco
         FPGA clk pin       ▼
                         Data ─── Ddata (device→FPGA) ───→ FPGA data pin

input_delay = Tco + Ddata - Dclk
VariableDefinition
TcoExternal device clock-to-output (datasheet)
DdataData flight time: external device → FPGA data pin
DclkClock flight time: clock source → FPGA clock pin
input_delay_max = Tco_max + Ddata_max - Dclk_min   (setup: late data, early clock)
input_delay_min = Tco_min + Ddata_min - Dclk_max   (hold: early data, late clock)

Concrete Example: Capturing ADC Data#

ParameterMinMaxFrom → To
ADC Tco0.8 ns2.2 nsADC clk → ADC data out
Ddata0.3 ns0.6 nsADC data out → FPGA data pin
Dclk0.2 ns0.5 nsClock source → FPGA clock pin
input_delay_max = 2.2 + 0.6 - 0.2 = 2.6 ns
input_delay_min = 0.8 + 0.3 - 0.5 = 0.6 ns
create_clock -name adc_clk -period 10.0 [get_ports adc_clk]
set_input_delay -clock adc_clk -max 2.6 [get_ports adc_data[*]]
set_input_delay -clock adc_clk -min 0.6 [get_ports adc_data[*]]

If Hold Fails After Adding Constraints#

That’s the tool finally modeling your board. Hold violations are worst at the fast-cold corner (fast silicon, low temperature).

Fixes: Add IDELAY to shift data later, adjust PCB skew, or change capture scheme.

Caution about IOB registers: Moving the capture FF into the IOB often helps setup but can hurt hold because data reaches the FF earlier.

Output Delay Math#

Reference: Capture clock edge at the external device.

VariableDefinition
TsuExternal device setup requirement (datasheet)
ThExternal device hold requirement (datasheet)
DdataData flight time: FPGA data pin → external device
DclkClock flight time: clock source → external device
output_delay_max = Tsu + Ddata_max - Dclk_min   (setup: late data, early clock)
output_delay_min = -(Th + Dclk_max - Ddata_min) (hold: early data, late clock)

Concrete Example: Driving a DAC#

Reference clock: Capture edge at the DAC.

ParameterMinMaxFrom → To
DAC Tsu-1.5 nsData stable before DAC captures
DAC Th0.5 ns-Data stable after DAC captures
Ddata0.4 ns0.6 nsFPGA data pin → DAC data pin
Dclk0.3 ns0.5 nsClock source → DAC clock pin
output_delay_max = 1.5 + 0.6 - 0.3 = 1.8 ns
output_delay_min = -(0.5 + 0.5 - 0.4) = -0.6 ns
# v_dac_clk represents the capture clock edge at the DAC (virtual clock)
create_clock -name v_dac_clk -period 10.0

set_output_delay -clock v_dac_clk -max 1.8 [get_ports dac_data[*]]
set_output_delay -clock v_dac_clk -min -0.6 [get_ports dac_data[*]]

About negative -min: A negative value can be valid - it means the allowed data transition window extends before the reference edge. This does not mean “you’re safe” unless you defined the clock at the correct capture point. If your reference point is wrong, STA will give you precise numbers about the wrong problem.

About virtual clocks: Virtual clocks model external requirements. If your output clock is derived from an internal clock with a known relationship, you may instead constrain against that internal clock at the appropriate reference point. The key is reference consistency.

Never Skip -min#

If you omit -min, you’re not modeling early data arrival (hold risk). Setting -min 0 can be a debug placeholder, but not signoff-ready.

Virtual Clocks#

Virtual clocks are for modeling external timing requirements, not internal sampling.

Do NOT use a virtual clock to “solve” CDC:

# WRONG: Creating virtual clock to hide CDC
create_clock -name v_ext_clk -period 8.0
set_clock_groups -asynchronous -group {my_clk} -group {v_ext_clk}
# This doesn't fix CDC. Data is still crossing domains unsynchronized.

Board Delays Unknown?#

Estimate ~150-180 ps/inch for FR4 (varies with stackup - use your board data, not this guess). Refine after layout.


Exceptions: Guilty Until Proven Innocent#

False Paths#

Target the first synchronizer stage. Over-matching is worse than no-matching - you can accidentally disable timing to paths that aren’t synchronizers.

# Target first sync stage using regex (brace-delimited for safety)
set_false_path -to [get_cells -hier -regexp -filter {NAME =~ {.*sync_reg\[0\].*} && IS_SEQUENTIAL}]

Do not false-path into later sync stages or the entire destination domain; only the first stage is allowed to violate timing by design.

Mandatory verification:

report_exceptions -verbose
# Confirm ONLY intended endpoints are affected

Name-based matching is brittle. Prefer marking sync flops with ASYNC_REG and targeting by that property if your flow supports it.

Constraints that match nothing are silent no-ops. Constraints that match too much silently disable real timing checks.

Multicycle Paths#

set_multicycle_path 2 -setup -from [get_cells slow_reg] -to [get_cells slow_dest]
set_multicycle_path 1 -hold  -from [get_cells slow_reg] -to [get_cells slow_dest]

Critical: When you relax setup by N cycles, relax hold by N-1 cycles.

Multicycle must match the actual enable/valid behavior. If the destination can sample on cycles you didn’t account for, multicycle is invalid. Validate with waveform-level reasoning.

Fanout trap: If slow_reg fans out to both multicycle and single-cycle destinations, only the specified path gets the exception.


The Audit#

Vivado Commands#

report_clocks
report_clock_interaction
check_timing -verbose
report_exceptions -verbose   # Verify exceptions hit intended targets

Quartus/TimeQuest Equivalents#

GoalVivadoQuartus/TimeQuest
List all clocksreport_clocksreport_clocks
Find constraint issuescheck_timingcheck_timing
Analyze clock transfersreport_clock_interactionreport_clock_transfers
Auto-derive PLL clocksClocking Wizard (auto)derive_pll_clocks
Auto-derive uncertaintyAutoderive_clock_uncertainty

Quartus versions differ. Goal is the same: zero unconstrained endpoints, all expected clocks present.


The Checklist#

  • Every external clock has create_clock
  • Every generated clock verified in report_clocks
  • No LUT-generated clocks for critical logic
  • Clock relationships verified in report_clock_interaction
  • CDC uses targeted false-paths to first sync stage (preferred) or set_clock_groups
  • CDC structures have ASYNC_REG on 2-flop synchronizers (verify placement)
  • I/O delays have consistent reference points (document “from → to”)
  • Every I/O has -min and -max delays
  • Every set_multicycle_path -setup N has matching -hold N-1
  • All exceptions verified with report_exceptions
  • Zero unresolved check_timing issues

Quick Reference#

The One-Liner: Constraints don’t make timing pass. They make timing real.

Common Mistakes:

MistakeSymptomFix
Missing generated clockTiming met, hardware failsVerify source create_clock, check report_clocks
Zero I/O delayHold violations at cornersCalculate real values
Multicycle without holdHold violationsAdd -hold N-1
False path over-matchesReal timing disabledVerify with report_exceptions
False path matches nothingSilent no-opVerify with report_exceptions
IOB reg to “fix” holdHold gets worseUse IDELAY instead
Mixed I/O reference pointsWrong constraint valuesDocument “from → to” consistently

Appendix: Example Constraints#

# ==============================================================================
# EXAMPLE - Calculate your own values from datasheets and layout.
# ==============================================================================

# ------------------------------------------------------------------------------
# Primary clock at FPGA input pin
# ------------------------------------------------------------------------------
create_clock -period 6.4 -name clk_156 [get_ports clk_in]

# Clock uncertainty: Tools model default values. For signoff, you need a
# justified uncertainty budget (jitter, SI, PLL). Don't adjust without data.

# ------------------------------------------------------------------------------
# Input delays - reference: clock at FPGA pin, data at FPGA pin
# ------------------------------------------------------------------------------
set_input_delay -clock clk_156 -max 5.5 [get_ports data_in[*]]
set_input_delay -clock clk_156 -min 1.5 [get_ports data_in[*]]

# ------------------------------------------------------------------------------
# Output delays - reference: capture clock at external device (virtual clock)
# ------------------------------------------------------------------------------
create_clock -name v_ext_clk -period 6.4
# No source - this is a virtual clock representing the device capture edge

set_output_delay -clock v_ext_clk -max 2.0 [get_ports data_out[*]]
set_output_delay -clock v_ext_clk -min -0.5 [get_ports data_out[*]]

# Note: This example uses FPGA-pin reference for inputs and device-capture
# reference for outputs - a common pattern. The key is consistency within
# each calculation, documented with "from → to".

# ------------------------------------------------------------------------------
# CDC - target first sync stage only, verify with report_exceptions
# ------------------------------------------------------------------------------
set_false_path -to [get_cells -hier -regexp -filter {NAME =~ {.*sync_reg\[0\].*} && IS_SEQUENTIAL}]

# ------------------------------------------------------------------------------
# Multicycle - always include hold adjustment
# ------------------------------------------------------------------------------
set_multicycle_path 2 -setup -from [get_cells slow_reg[*]] -to [get_cells slow_dest[*]]
set_multicycle_path 1 -hold  -from [get_cells slow_reg[*]] -to [get_cells slow_dest[*]]

These constraints compile but aren’t correct for your board. Use the formulas and checklists above to calculate values for your actual hardware.


Timing Series#

  1. Your FPGA Lives a Lifetime While You Blink - Why timing satisfies or breaks
  2. Constraints: The Contract You Forgot to Sign - How to write constraints (you are here)
  3. Understanding Timing Analysis - How to read timing reports
  4. Pipelining Without Breaking Your Protocol - How to fix violations
  5. Silicon Real Estate: Your Resource Budget - How to manage resources
  6. CDC: Two Flip-Flops Are Not Magic - How to cross clock domains
  7. Resets: The Timing Event You Forgot - How to handle resets