False paths and max delay
You will learn
What a false path declares, what a max delay bounds instead, and what removing a check looks like in a repository.
set_false_path tells the analyzer a crossing is asynchronous: stop reporting it, the synchronizer owns it. Used beyond that, it silences real failures, so a CDC flow pairs it with set_max_delay, which bounds a synchronizer path instead of ignoring it -- the two or three flops must still be close in time. A false path is a check deliberately removed, and the widget shows what removing checks looks like in a repository: 14 days of merged pull requests, which added tests, changed them or removed asserts, with the exact lines from git. The spec frame opens timing.t27, the analysis the exceptions carve holes in.
Try it
In the widget, find a pull request that removed asserts and the exact lines it took; then in the spec frame find the slack arithmetic a false path silences.

Smallest hold 3445 ps on TXD1 against a 1000 ps bench; each TX pin's setup and hold from nextpnr's own SDF of the routed node.
specs/fpga/timing.t27
// SPDX-License-Identifier: Apache-2.0
// t27/specs/fpga/timing.t27
// T27 Static Timing Analysis Specification
// Estimates critical path, slack, and Fmax from HIR module structure
// Artix-7 timing model: LUT=0.1ns, BRAM=2.0ns, DSP=2.5ns, routing=0.3ns
// Uses flat arrays + count fields (parser-compatible)
// phi^2 + 1/phi^2 = 3 | TRINITY
module Timing {
// === Timing arc kind ===
pub const ArcKind = enum(i8) {
comb = 0,
reg_to_reg = 1,
reg_to_output = 2,
input_to_reg = 3,
input_to_output = 4,
}
// === Timing arc ===
pub struct TimingArc {
source : &str,
sink : &str,
delay_ps : u32,
kind : i8,
}
fn comb_arc(source: &str, sink: &str, delay_ps: u32) -> TimingArc {
return TimingArc{
.source = source,
.sink = sink,
.delay_ps = delay_ps,
.kind = 0,
};
}
fn reg_to_reg(source: &str, sink: &str, delay_ps: u32) -> TimingArc {
return TimingArc{
.source = source,
.sink = sink,
.delay_ps = delay_ps,
.kind = 1,
};
}
fn input_to_reg(source: &str, sink: &str, delay_ps: u32) -> TimingArc {
return TimingArc{
.source = source,
.sink = sink,
.delay_ps = delay_ps,
.kind = 3,
};
}
// === Timing path ===
pub struct TimingPath {
startpoint : &str,
endpoint : &str,
total_delay_ps : u32,
slack_ps : i64,
num_arcs : u32,
}
fn timing_path(start: &str, end: &str, delay: u32, slack: i64) -> TimingPath {
return TimingPath{
.startpoint = start,
.endpoint = end,
.total_delay_ps = delay,
.slack_ps = slack,
.num_arcs = 1,
};
}
fn is_met(path: TimingPath) -> bool {
return path.slack_ps >= 0;
}
fn is_violated(path: TimingPath) -> bool {
return path.slack_ps < 0;
}
// === Timing constraint ===
pub struct TimingConstraint {
name : &str,
period_ps : u32,
clock_name : &str,
}
fn clock_constraint(name: &str, period_ps: u32) -> TimingConstraint {
return TimingConstraint{
.name = name,
.period_ps = period_ps,
.clock_name = "clk",
};
}
fn clock_mhz(name: &str, mhz: u32) -> TimingConstraint {
if mhz == 0 {
return clock_constraint(name, 10000);
}
return TimingConstraint{
.name = name,
.period_ps = 1000000000 / mhz,
.clock_name = "clk",
};
}
// === Timing report ===
pub struct TimingReport {
total_paths : u32,
met_paths : u32,
violated_paths : u32,
worst_slack_ps : i64,
critical_path_ps : u32,
fmax_mhz : u32,
has_violations : bool,
}
fn timing_ok(critical_ps: u32, fmax: u32) -> TimingReport {
return TimingReport{
.total_paths = 1,
.met_paths = 1,
.violated_paths = 0,
.worst_slack_ps = 5000,
.critical_path_ps = critical_ps,
.fmax_mhz = fmax,
.has_violations = false,
};
}
fn timing_fail(critical_ps: u32) -> TimingReport {
return TimingReport{
.total_paths = 1,
.met_paths = 0,
.violated_paths = 1,
.worst_slack_ps = -1000,
.critical_path_ps = critical_ps,
.fmax_mhz = 0,
.has_violations = true,
};
}
fn passed(report: TimingReport) -> bool {
return report.has_violations == false;
}
// === Timing model constants ===
fn lut_delay_ps() -> u32 {
return 100;
}
fn bram_delay_ps() -> u32 {
return 2000;
}
fn dsp_delay_ps() -> u32 {
return 2500;
}
fn routing_delay_ps() -> u32 {
return 300;
}
fn setup_time_ps() -> u32 {
return 200;
}
fn hold_time_ps() -> u32 {
return 50;
}
// === Query functions ===
fn path_delay(arcs: [TimingArc], count: u32) -> u32 {
var total : u32 = 0;
var i : u32 = 0;
while i < count {
total = total + arcs[i].delay_ps;
i = i + 1;
}
return total;
}
fn slack(delay_ps: u32, constraint_ps: u32) -> i64 {
return constraint_ps as i64 - delay_ps as i64;
}
fn fmax_from_delay(delay_ps: u32) -> u32 {
if delay_ps == 0 {
return 0;
}
return 1000000000 / delay_ps;
}
fn est_comb_delay(num_luts: u32) -> u32 {
return num_luts * lut_delay_ps() + routing_delay_ps();
}
fn est_reg_to_reg_delay(num_luts: u32) -> u32 {
return num_luts * lut_delay_ps() + routing_delay_ps() + setup_time_ps();
}
fn worst_path(paths: [TimingPath], count: u32) -> u32 {
if count == 0 {
return 0;
}
var worst : u32 = paths[0].total_delay_ps;
var i : u32 = 1;
while i < count {
if paths[i].total_delay_ps > worst {
worst = paths[i].total_delay_ps;
}
i = i + 1;
}
return worst;
}
// === Validation ===
fn validate_constraint(tc: TimingConstraint) -> u32 {
var errors : u32 = 0;
if tc.name == "" {
errors = errors + 1;
}
if tc.period_ps == 0 {
errors = errors + 1;
}
return errors;
}
fn validate_arc(arc: TimingArc) -> u32 {
var errors : u32 = 0;
if arc.source == "" {
errors = errors + 1;
}
if arc.sink == "" {
errors = errors + 1;
}
return errors;
}
// === Tests ===
test comb_arc_creation
given a = comb_arc("a", "b", 500)
then a.source == "a"
and a.sink == "b"
and a.delay_ps == 500
and a.kind == 0
test reg_to_reg_creation
given a = reg_to_reg("r1", "r2", 800)
then a.kind == 1
test input_to_reg_creation
given a = input_to_reg("din", "r1", 400)
then a.kind == 3
test timing_path_met
given p = timing_path("r1", "r2", 5000, 5000)
then is_met(p) == true
and is_violated(p) == false
test timing_path_violated
given p = timing_path("r1", "r2", 12000, -2000)
then is_met(p) == false
and is_violated(p) == true
test clock_constraint_creation
given c = clock_constraint("clk_fast", 5000)
then c.period_ps == 5000
and c.clock_name == "clk"
test clock_mhz_creation
given c = clock_mhz("clk_100", 100)
then c.period_ps == 10000000
and c.name == "clk_100"
test clock_mhz_zero
given c = clock_mhz("bad", 0)
then c.period_ps == 10000
test timing_ok_report
given r = timing_ok(5000, 200)
then r.critical_path_ps == 5000
and r.fmax_mhz == 200
and passed(r) == true
test timing_fail_report
given r = timing_fail(15000)
then r.has_violations == true
and passed(r) == false
test path_delay_calc
given a1 = comb_arc("a", "b", 100)
and a2 = comb_arc("b", "c", 200)
and a3 = comb_arc("c", "d", 300)
then path_delay([a1, a2, a3], 3) == 600
test slack_positive
then slack(5000, 10000) == 5000
test slack_negative
then slack(15000, 10000) == -5000
test fmax_from_delay
then fmax_from_delay(5000) == 200000
test fmax_zero_delay
then fmax_from_delay(0) == 0
test est_comb_delay
then est_comb_delay(3) == 600
test est_reg_to_reg_delay
then est_reg_to_reg_delay(3) == 800
test worst_path
given p1 = timing_path("a", "b", 500, 0)
and p2 = timing_path("c", "d", 1200, 0)
and p3 = timing_path("e", "f", 800, 0)
then worst_path([p1, p2, p3], 3) == 1200
test worst_path_empty
then worst_path([], 0) == 0
test validate_constraint_ok
given c = clock_constraint("clk", 10000)
then validate_constraint(c) == 0
test validate_constraint_empty_name
given c = TimingConstraint{.name = "", .period_ps = 10000, .clock_name = "clk"}
then validate_constraint(c) > 0
test validate_arc_ok
given a = comb_arc("a", "b", 100)
then validate_arc(a) == 0
test validate_arc_empty_source
given a = TimingArc{.source = "", .sink = "b", .delay_ps = 100, .kind = 0}
then validate_arc(a) > 0
test timing_model_constants
then lut_delay_ps() == 100
and bram_delay_ps() == 2000
and dsp_delay_ps() == 2500
and routing_delay_ps() == 300
and setup_time_ps() == 200
test hold_time_ps
then hold_time_ps() == 50
// === Invariants ===
invariant slack_consistent_with_fmax
given d = 5000
and s = slack(d, 10000)
assert s >= 0
invariant timing_constants_positive
assert lut_delay_ps() > 0
and bram_delay_ps() > lut_delay_ps()
and dsp_delay_ps() > lut_delay_ps()
// === Benchmarks ===
bench timing_analysis
measure: nanoseconds for est_reg_to_reg_delay(10)
target: < 50ns
}
// phi^2 + 1/phi^2 = 3 | TRINITY
All lessons
Module 1 · What a clock is
One edge, one world: what shares a clock edge shares a world, the period and the jitter of a real edge, and where the clock enters a board.
Module 2 · Clock trees
Skew and insertion delay, the global buffer network, and the trap of gating a clock with logic.
Module 3 · PLL and MMCM
Multiply and divide one clock into another, move its phase in steps of the VCO, and which clocks the analyzer treats as related.
Module 4 · Resets
Assert asynchronously, release synchronously: the three reset kinds, the release pipe, and the tree a reset grows.
Module 5 · Metastability
The setup-hold window, the mean time between failures in integer arithmetic, and the two flops that fix it.
Module 6 · Crossing many bits
Why a binary bus tears, why Gray code does not, and the handshake that moves a pulse between worlds.
Module 7 · The asynchronous FIFO
Pointers, flags and depth: the buffer that moves a stream between two clocks.
Module 8 · Constraints
The lines that tell the analyzer what a clock is, which paths not to check, and what the pins must meet.
Module 9 · On the board
A CDC report, one crossing captured at the flip-flops, and the bitstream diff that closes the course.