t27.aiРусский

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.

Open the interactive lesson →

tri fpga-txhold: setup and hold from the routed SDF
tri fpga-txhold: setup and hold from the routed SDF ↗

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

Open the lesson's spec in the player ↗

All lessons