hexforge / timing analysis
Timing analysis
The analyser walks the routed design, adds up cell and wire delays along every register-to-register path, and tells you the worst one per clock. It is a static check against a measured delay table, and its accuracy is limited by how that table was built.
A full report
$ hxta --device T4-25K --corner slow uart_loopback.hxr
hxta 0.7.2 design uart_loopback device T4-25K
corner slow: 85 C, 1.14 V, delay table t4.hxd rev 11 (2026-02-18)
clock groups
name target period achieved slack status
clk_12m 12.00 MHz 83.333 ns 71.4 MHz +69.333 ns PASS
clk_tx 48.00 MHz 20.833 ns 52.1 MHz +1.646 ns PASS
2 clocks, 1 asynchronous crossing NOT analysed (see warnings)
worst path, clock clk_tx, slack +1.646 ns
start tx_div.count[4] FF/Q 0.000 ns 0.000
cell lut4 u_2871 A -> Y 0.412 ns 0.412
route net n_4412 (3 hops) 1.087 ns 1.499
cell lut4 u_2903 B -> Y 0.398 ns 1.897
route net n_4418 (5 hops) 2.241 ns 4.138
cell lut4 u_2911 C -> Y 0.431 ns 4.569
route net n_4423 (2 hops) 0.766 ns 5.335
cell lut4 u_2960 A -> Y 0.407 ns 5.742
route net n_4431 (9 hops) 4.912 ns 10.654
end tx_shift.data[7] FF/D 0.000 ns 10.654
setup 0.182 ns 10.836
clock skew start to end -0.148 ns 10.688
required (period 20.833, uncertainty 0.250) 20.583
slack +1.646
paths 2 to 10 by slack
+1.812 ns tx_div.count[4] -> tx_shift.data[6]
+2.004 ns tx_div.count[3] -> tx_shift.data[7]
+2.119 ns rx_sync.stage[1] -> rx_fsm.state[2]
+2.640 ns tx_div.count[2] -> tx_shift.data[5]
+3.118 ns rx_fsm.state[0] -> rx_fsm.state[1]
+3.402 ns rx_fsm.state[2] -> tx_load
+4.001 ns tx_shift.data[0] -> uart_tx_pin
+5.277 ns rx_sync.stage[0] -> rx_sync.stage[1]
+6.015 ns rx_fsm.state[1] -> rx_err
warnings
W204 clk_12m -> clk_tx crossing on net rx_sync.stage[0]: no
constraint, path excluded from analysis
W117 one route has 9 hops; delay estimates degrade above 6
2 clocks analysed, 0 violations, 2 warnings
Reading the path listing
Each line of the path is a delay and a running total. The right-hand column is cumulative, so the last route line being 4.912 ns of a 10.654 ns path is the whole story of this design: half the critical path is one badly routed net with nine hops. The placer put those two cells far apart because it optimises wirelength globally and knows nothing about this path, which is the structural weakness described on the build flow page.
Two practical consequences. First, re-running hxsyn --seed 3 and re-checking will often move a path like this by a nanosecond or more. Second, warning W117 means the number you are reading is the least trustworthy kind: the delay table was fitted on routes of two to six hops, and beyond that the model extrapolates.
The W204 warning deserves attention rather than irritation. Those two clocks are unrelated, the analyser cannot check the crossing, and it says so instead of quietly reporting a pass. A missing clock-domain constraint is the single most common reason a design passes timing here and misbehaves on the board.
What the model contains
- A delay per cell type and input pin, at three corners:
fast(0 °C, 1.26 V),typical(25 °C, 1.20 V) andslow(85 °C, 1.14 V). - A per-hop wire delay by routing resource class, fitted linearly against hop count.
- Setup and hold times for flip-flops, and clock-to-output for registered IO.
- Global clock network skew, as a single number per device, measured once.
- Clock uncertainty, fixed at 0.250 ns and added to every required time.
Those numbers come from ring oscillators of varying length built on real boards and fitted per cell type. Accuracy is about ±12% and, importantly, the error is biased: long routes come out faster in the model than in silicon. That is the opposite of the direction you want to be wrong in.
What it ignores
- Hold checks. Not implemented. On these devices with a single global clock network and no local skew modelling, hold violations within one clock domain are unlikely, but "unlikely" is not "checked".
- Clock-domain crossings. Reported as a warning and excluded. There is no synchroniser recognition and no metastability analysis.
- Recovery and removal on asynchronous resets.
- On-die variation. One delay number per cell type across the whole die.
- Crosstalk and supply droop. Entirely absent from the model.
- Input and output delays unless you declared them with
set_input_delayorset_output_delay. Undeclared IO timing is assumed ideal, which it never is. - Hard block internals. Block RAM is modelled with one access time; PLL jitter is not modelled at all.
How much margin to keep
Treat a slow-corner result as the truth minus 15%. If a report says 52.1 MHz, plan for 44 MHz and verify on hardware. Designs that pass with less than roughly 10% slack have failed on boards here often enough that the figure is a rule of thumb rather than caution: three of the eight examples needed a seed change to clear it.
Where the margin genuinely is not there, the options are the boring ones: add a pipeline register, split a wide combinational expression, lower the clock, or try four seeds and keep the best. The toolchain will not retime anything for you, and it will not insert a register it was not asked for.
Flags
| Flag | Default | Effect |
|---|---|---|
--device <part> | - | Required. Must match the device the design was routed for, or the run aborts. |
--corner <c> | slow | fast, typical or slow. Only slow is a defensible basis for a decision. |
--paths <n> | 10 | How many paths to list after the worst one. |
--clock <name> | all | Restrict the report to one clock group. |
--json | off | Machine-readable output. This format is stable; the text layout is not, and realigns whenever a column grows. |
--fail-on-violation | off | Exit code 1 when any slack is negative, for use in a build script. |
Exit codes without that last flag: 0 for a completed analysis, 2 when the routed design and the device do not match, 3 when the delay table revision is older than the device file expects.