Родственные или чужие
Вы узнаете
Когда анализатор проверяет путь между двумя тактовыми сигналами и когда ему надо сказать, что они чужие.
Два тактовых сигнала родственны, когда один сделан из другого — через MMCM из модуля 3 — и тогда каждый путь между ними хронометрируется: анализатор знает об общем происхождении. Чужим такая проверка не положена, пока вы её не объявите. timing.t27 — арифметика этой проверки: дуги, запас (slack) и Fmax с учебной моделью Artix-7: LUT 0,1 нс, BRAM 2,0 нс, DSP 2,5 нс, трассировка 0,3 нс. Запись запускает нативный t27c: проходят 25 тестов, 2 инварианта comptime. Родственные клоки были модулем 3; безопасность чужих — это модули с 5 по 7.
Попробуйте
В записи посчитайте прошедшие тесты; затем в окне спеки найдите четыре учебные задержки шапки и функцию, превращающую дуги в запас.

t27c 0.4.0 on a laptop (macOS): 25 tests of the timing spec pass natively, 2 invariants comptime -- arcs, slack, Fmax and the Artix-7 teaching delays (LUT 0.1 ns, BRAM 2.0 ns, DSP 2.5 ns).
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
Все уроки
Модуль 1 · Что такое тактовый сигнал
Один фронт, один мир: всё, что делит фронт тактового сигнала, делит и мир; период и джиттер настоящего фронта и место, где тактовый сигнал входит в плату.
Модуль 2 · Деревья тактового сигнала
Перекос и задержка прохождения, глобальная сеть буферов и ловушка стробирования клока логикой.
Модуль 3 · PLL и MMCM
Умножить и поделить один тактовый сигнал в другой, сдвинуть его фазу шагами VCO и какие тактовые сигналы анализатор считает родственными.
Модуль 4 · Сбросы
Ассертировать асинхронно, снимать синхронно: три вида сброса, конвейер снятия и дерево, которое растит сброс.
Модуль 5 · Метастабильность
Окно setup-hold, среднее время между отказами в целочисленной арифметике и два триггера, которые это чинят.
Модуль 6 · Пересечение многих бит
Почему двоичная шина рвётся, почему код Грея нет, и рукопожатие, переносящее импульс между мирами.
Модуль 7 · Асинхронный FIFO
Указатели, флаги и глубина: буфер, переносящий поток между двумя тактовыми сигналами.
Модуль 8 · Ограничения
Строки, сообщающие анализатору, что такое тактовый сигнал, какие пути не проверять и чему должны соответствовать выводы.
Модуль 9 · На плате
Отчёт CDC, одно пересечение, захваченное на триггерах, и дифф битстрима, замыкающий курс.