t27.aiEnglish

Ловушка стробированного клока

Вы узнаете

Почему стробирование клока логикой создаёт новый домен и зачем нужен clock enable.

Стробировать клок логикой — элемент AND на линии тактового сигнала ради экономии — значит резать клок сигналом, который меняется со скоростью данных: получаются глитчи, и тихо возникает новый тактовый домен из всего, что ниже по потоку. Правило: не стробируйте клок; используйте clock enable — его сэмплирует триггер на фронте, а не фронт сэмплирует его. Виджет показывает дисциплину доказательства таких утверждений: два битстрима, различающихся в трёх битах, и выбивающая сборка на каждый бит, каждая снимает один — ни один бит не может приписать себе незаслуженную заслугу. Окно спеки открывает clock_domain.t27, чей тип источника gated_clk — модель этой ловушки.

Попробуйте

В виджете найдите, на сколько бит различаются битстримы и как снимали каждый; затем в окне спеки найдите тип источника gated-clock и значение перечисления стратегий, которое вынуждает стробированный клок.

Открыть интерактивный урок →

Race the bee: who lands patches on t27, a person or a bee
Race the bee: who lands patches on t27, a person or a bee ↗

A dated snapshot of gHashTag/t27: merged pull requests by the Queen's bees and by people, the issues both lanes went for, and the open ones nobody has taken.

specs/fpga/clock_domain.t27

// SPDX-License-Identifier: Apache-2.0
// t27/specs/fpga/clock_domain.t27
// Clock Domain Abstraction for Trinity T27 FPGA HIR
// Defines clock sources, PLL configs, and cross-domain crossing
// Uses flat structs (parser-compatible)
// phi^2 + 1/phi^2 = 3 | TRINITY

module ClockDomain {

    // === Clock source kind ===

    pub const ClkSrcKind = enum(i8) {
        external = 0,
        pll = 1,
        dcm = 2,
        mmcm = 3,
    };

    // === Clock edge ===

    pub const ClkEdge = enum(i8) {
        posedge = 0,
        negedge = 1,
    };

    // === Crossing strategy ===

    pub const CrossStrategy = enum(i8) {
        no_cross = 0,
        two_flop = 1,
        fifo_async = 2,
        handshake = 3,
    };

    // === Clock source descriptor ===

    pub struct ClkSource {
        name : &str,
        kind : i8,
        freq_hz : u32,
        phase_deg : u32,
        jitter_ps : u32,
    }

    // === Clock domain descriptor ===

    pub struct ClkDomain {
        name : &str,
        source_name : &str,
        freq_hz : u32,
        edge : i8,
    }

    // === Cross-domain crossing descriptor ===

    pub struct ClockCrossing {
        src_domain : &str,
        dst_domain : &str,
        strategy : i8,
        data_width : u32,
    }

    // === Constructor helpers ===

    fn ext_clock(name: &str, freq_hz: u32) -> ClkSource {
        return ClkSource{
            .name = name,
            .kind = 0,
            .freq_hz = freq_hz,
            .phase_deg = 0,
            .jitter_ps = 0,
        };
    }

    fn pll_clock(name: &str, freq_hz: u32, phase_deg: u32) -> ClkSource {
        return ClkSource{
            .name = name,
            .kind = 1,
            .freq_hz = freq_hz,
            .phase_deg = phase_deg,
            .jitter_ps = 0,
        };
    }

    fn make_domain(name: &str, source_name: &str, freq_hz: u32) -> ClkDomain {
        return ClkDomain{
            .name = name,
            .source_name = source_name,
            .freq_hz = freq_hz,
            .edge = 0,
        };
    }

    fn make_crossing(src: &str, dst: &str, strategy: i8, data_width: u32) -> ClockCrossing {
        return ClockCrossing{
            .src_domain = src,
            .dst_domain = dst,
            .strategy = strategy,
            .data_width = data_width,
        };
    }

    // === Query functions ===

    fn is_external(src: ClkSource) -> bool {
        return src.kind == 0;
    }

    fn is_pll(src: ClkSource) -> bool {
        return src.kind == 1;
    }

    fn period_ns(domain: ClkDomain) -> u32 {
        if (domain.freq_hz == 0) {
            return 0;
        }
        return 1000000000 / domain.freq_hz;
    }

    fn half_period_ns(domain: ClkDomain) -> u32 {
        return period_ns(domain) / 2;
    }

    fn same_domain(a: ClkDomain, b: ClkDomain) -> bool {
        return a.name == b.name;
    }

    fn needs_crossing(a: ClkDomain, b: ClkDomain) -> bool {
        if (a.name == b.name) {
            return false;
        }
        if (a.freq_hz == b.freq_hz and a.source_name == b.source_name) {
            return false;
        }
        return true;
    }

    fn crossing_data_bits(cross: ClockCrossing) -> u32 {
        return cross.data_width;
    }

    fn is_async_cross(cross: ClockCrossing) -> bool {
        return cross.strategy == 2;
    }

    // === Tests ===

    test ext_clock_is_external
        given c = ext_clock("sys_clk", 12000000)
        then is_external(c) == true
        and is_pll(c) == false

    test pll_clock_is_pll
        given c = pll_clock("pll_clk", 100000000, 0)
        then is_pll(c) == true
        and is_external(c) == false

    test period_12mhz
        given d = make_domain("sys", "sys_clk", 12000000)
        then period_ns(d) == 83

    test period_100mhz
        given d = make_domain("fast", "pll_clk", 100000000)
        then period_ns(d) == 10

    test half_period
        given d = make_domain("sys", "sys_clk", 12000000)
        then half_period_ns(d) == 41

    test same_domain_true
        given a = make_domain("sys", "clk", 12000000)
        then same_domain(a, a) == true

    test same_domain_false
        given a = make_domain("sys", "clk", 12000000)
        and b = make_domain("fast", "pll", 100000000)
        then same_domain(a, b) == false

    test needs_crossing_diff_freq
        given a = make_domain("sys", "clk", 12000000)
        and b = make_domain("fast", "pll", 100000000)
        then needs_crossing(a, b) == true

    test needs_crossing_same
        given a = make_domain("sys", "clk", 12000000)
        then needs_crossing(a, a) == false

    test crossing_data_bits
        given c = make_crossing("sys", "fast", 1, 32)
        then crossing_data_bits(c) == 32

    test async_cross_fifo
        given c = make_crossing("sys", "fast", 2, 16)
        then is_async_cross(c) == true

    test sync_cross_not_async
        given c = make_crossing("sys", "fast", 1, 8)
        then is_async_cross(c) == false

    // === Invariants ===

    invariant ext_clock_has_freq
        given c = ext_clock("inv", 12000000)
        assert c.freq_hz > 0

    invariant period_positive_for_valid_freq
        given d = make_domain("inv", "clk", 12000000)
        assert period_ns(d) > 0

    invariant half_period_half_of_period
        given d = make_domain("inv", "clk", 12000000)
        assert half_period_ns(d) == period_ns(d) / 2

    invariant same_domain_reflexive
        given d = make_domain("inv", "clk", 12000000)
        assert same_domain(d, d) == true

    invariant needs_crossing_symmetric
        given a = make_domain("a", "clk", 12000000)
        and b = make_domain("b", "pll", 100000000)
        assert needs_crossing(a, b) == needs_crossing(b, a)

    // === Benchmarks ===

    bench period_ns_latency
        measure: nanoseconds to period_ns(make_domain("b", "c", 100000000))
        target: < 50ns
}

// phi^2 + 1/phi^2 = 3 | TRINITY

Открыть спеку урока в плеере ↗

Все уроки