t27.aiРусский

FSM coverage

You will learn

How state and transition coverage see the arcs a run never took.

A state machine is verified by its arcs, not its states: 4 states with 12 transitions is 12 ways to be wrong, and visiting every state can leave half the arcs cold. The spec models it directly: state points and transition points, each with a hit count, and illegal transitions that fail the report the moment one warms. The widget is the discipline in another shape: 112 files pinned by sha256 in specs, every one accounted for -- 0 lost, 0 not kept, 0 unlocated. Nothing on the list is allowed to be unvisited.

Try it

List the arcs of a 4-state FIFO FSM and mark which a read-only workload leaves cold; then find the illegal arcs in the spec and what warms them.

Open the interactive lesson →

tri fpga-tmpcheck · every /tmp file a spec pins, kept
tri fpga-tmpcheck · every /tmp file a spec pins, kept ↗

112 files under /tmp are pinned by sha256 in specs. /tmp is emptied at boot and all 112 are gone from it; every one has a kept copy. 0 lost, 0 not kept, 0 unlocated.

specs/fpga/coverage.t27

// SPDX-License-Identifier: Apache-2.0
// t27/specs/fpga/coverage.t27
// T27 Coverage Model Specification: line, toggle and FSM coverage
// Answers one question for every simulation: what did the tests touch?
// A point with hit_count == 0 is a promise the tests never kept.
// Uses flat arrays + count fields (parser-compatible)
// phi^2 + 1/phi^2 = 3 | TRINITY

module Coverage {

    // === Coverage point kind ===

    pub const CoverageKind = enum(i8) {
        line = 0,
        toggle = 1,
        fsm_state = 2,
        fsm_transition = 3,
        branch = 4,
    }

    // === Line / branch point ===

    pub struct LinePoint {
        name : &str,
        module_name : &str,
        kind : i8,
        line_no : u32,
        hit_count : u32,
    }

    // === Toggle point: one signal bit, one direction ===

    pub struct TogglePoint {
        name : &str,
        module_name : &str,
        bit_index : u32,
        rise : bool,
        hit_count : u32,
    }

    // === FSM state visit ===

    pub struct FsmStatePoint {
        fsm_name : &str,
        state_name : &str,
        hit_count : u32,
    }

    // === FSM transition visit; illegal ones must stay at 0 hits ===

    pub struct FsmTransitionPoint {
        fsm_name : &str,
        from_state : &str,
        to_state : &str,
        illegal : bool,
        hit_count : u32,
    }

    // === Coverage report ===

    pub struct CoverageReport {
        name : &str,
        points_total : u32,
        points_hit : u32,
        cycles : u32,
    }

    // === Constructor helpers ===

    fn line_point(name: &str, module_name: &str, line_no: u32) -> LinePoint {
        return LinePoint{
            .name = name,
            .module_name = module_name,
            .kind = 0,
            .line_no = line_no,
            .hit_count = 0,
        };
    }

    fn branch_point(name: &str, module_name: &str, line_no: u32) -> LinePoint {
        return LinePoint{
            .name = name,
            .module_name = module_name,
            .kind = 4,
            .line_no = line_no,
            .hit_count = 0,
        };
    }

    fn toggle_rise(name: &str, module_name: &str, bit_index: u32) -> TogglePoint {
        return TogglePoint{
            .name = name,
            .module_name = module_name,
            .bit_index = bit_index,
            .rise = true,
            .hit_count = 0,
        };
    }

    fn toggle_fall(name: &str, module_name: &str, bit_index: u32) -> TogglePoint {
        return TogglePoint{
            .name = name,
            .module_name = module_name,
            .bit_index = bit_index,
            .rise = false,
            .hit_count = 0,
        };
    }

    fn fsm_state(fsm_name: &str, state_name: &str) -> FsmStatePoint {
        return FsmStatePoint{
            .fsm_name = fsm_name,
            .state_name = state_name,
            .hit_count = 0,
        };
    }

    fn fsm_transition(fsm_name: &str, from_state: &str, to_state: &str) -> FsmTransitionPoint {
        return FsmTransitionPoint{
            .fsm_name = fsm_name,
            .from_state = from_state,
            .to_state = to_state,
            .illegal = false,
            .hit_count = 0,
        };
    }

    fn illegal_transition(fsm_name: &str, from_state: &str, to_state: &str) -> FsmTransitionPoint {
        return FsmTransitionPoint{
            .fsm_name = fsm_name,
            .from_state = from_state,
            .to_state = to_state,
            .illegal = true,
            .hit_count = 0,
        };
    }

    // === Hit accounting: copies, never side effects ===

    fn mark_hit_line(p: LinePoint) -> LinePoint {
        var result = p;
        result.hit_count = p.hit_count + 1;
        return result;
    }

    fn mark_hit_toggle(p: TogglePoint) -> TogglePoint {
        var result = p;
        result.hit_count = p.hit_count + 1;
        return result;
    }

    fn mark_hit_state(p: FsmStatePoint) -> FsmStatePoint {
        var result = p;
        result.hit_count = p.hit_count + 1;
        return result;
    }

    fn mark_hit_transition(p: FsmTransitionPoint) -> FsmTransitionPoint {
        var result = p;
        result.hit_count = p.hit_count + 1;
        return result;
    }

    fn is_covered_line(p: LinePoint) -> bool {
        return p.hit_count > 0;
    }

    fn is_covered_toggle(p: TogglePoint) -> bool {
        return p.hit_count > 0;
    }

    fn is_covered_state(p: FsmStatePoint) -> bool {
        return p.hit_count > 0;
    }

    fn is_covered_transition(p: FsmTransitionPoint) -> bool {
        return p.hit_count > 0;
    }

    // === Report math: integer percent, 0 when nothing was measured ===

    fn hit_lines(points: [LinePoint], count: u32) -> u32 {
        var hit : u32 = 0;
        var i : u32 = 0;
        while i < count {
            if points[i].hit_count > 0 {
                hit = hit + 1;
            }
            i = i + 1;
        }
        return hit;
    }

    fn line_coverage_percent(points: [LinePoint], count: u32) -> u32 {
        if count == 0 {
            return 0;
        }
        return (hit_lines(points, count) * 100) / count;
    }

    fn hit_toggles(points: [TogglePoint], count: u32) -> u32 {
        var hit : u32 = 0;
        var i : u32 = 0;
        while i < count {
            if points[i].hit_count > 0 {
                hit = hit + 1;
            }
            i = i + 1;
        }
        return hit;
    }

    fn toggle_coverage_percent(points: [TogglePoint], count: u32) -> u32 {
        if count == 0 {
            return 0;
        }
        return (hit_toggles(points, count) * 100) / count;
    }

    // === FSM: every state and arc; illegal arcs must stay cold ===

    fn hit_states(points: [FsmStatePoint], count: u32) -> u32 {
        var hit : u32 = 0;
        var i : u32 = 0;
        while i < count {
            if points[i].hit_count > 0 {
                hit = hit + 1;
            }
            i = i + 1;
        }
        return hit;
    }

    fn hit_transitions(points: [FsmTransitionPoint], count: u32) -> u32 {
        var hit : u32 = 0;
        var i : u32 = 0;
        while i < count {
            if points[i].hit_count > 0 {
                hit = hit + 1;
            }
            i = i + 1;
        }
        return hit;
    }

    fn illegal_hits(points: [FsmTransitionPoint], count: u32) -> u32 {
        var hit : u32 = 0;
        var i : u32 = 0;
        while i < count {
            if points[i].illegal == true {
                if points[i].hit_count > 0 {
                    hit = hit + 1;
                }
            }
            i = i + 1;
        }
        return hit;
    }

    fn fsm_report(name: &str, states: [FsmStatePoint], state_count: u32,
                  transitions: [FsmTransitionPoint], transition_count: u32) -> CoverageReport {
        return CoverageReport{
            .name = name,
            .points_total = state_count + transition_count,
            .points_hit = hit_states(states, state_count) + hit_transitions(transitions, transition_count),
            .cycles = 0,
        };
    }

    fn report_percent(r: CoverageReport) -> u32 {
        if r.points_total == 0 {
            return 0;
        }
        return (r.points_hit * 100) / r.points_total;
    }

    // === Validation ===

    fn validate_line(p: LinePoint) -> u32 {
        var errors : u32 = 0;
        if p.name == "" {
            errors = errors + 1;
        }
        if p.module_name == "" {
            errors = errors + 1;
        }
        if p.line_no == 0 {
            errors = errors + 1;
        }
        return errors;
    }

    fn validate_toggle(p: TogglePoint) -> u32 {
        var errors : u32 = 0;
        if p.name == "" {
            errors = errors + 1;
        }
        if p.module_name == "" {
            errors = errors + 1;
        }
        return errors;
    }

    fn validate_state(p: FsmStatePoint) -> u32 {
        var errors : u32 = 0;
        if p.fsm_name == "" {
            errors = errors + 1;
        }
        if p.state_name == "" {
            errors = errors + 1;
        }
        return errors;
    }

    fn validate_transition(p: FsmTransitionPoint) -> u32 {
        var errors : u32 = 0;
        if p.fsm_name == "" {
            errors = errors + 1;
        }
        if p.from_state == "" {
            errors = errors + 1;
        }
        if p.to_state == "" {
            errors = errors + 1;
        }
        return errors;
    }

    fn validate_report(r: CoverageReport) -> u32 {
        var errors : u32 = 0;
        if r.name == "" {
            errors = errors + 1;
        }
        if r.points_hit > r.points_total {
            errors = errors + 1;
        }
        return errors;
    }

    // === Tests ===

    test line_point_creation
        given p = line_point("uart_tx.latch", "UART_TX", 42)
        then p.kind == 0
        and p.line_no == 42
        and p.hit_count == 0

    test branch_point_creation
        given p = branch_point("fifo.full", "FIFO", 88)
        then p.kind == 4
        and p.line_no == 88

    test toggle_points_creation
        given rise = toggle_rise("txd", "UART_TX", 0)
        and fall = toggle_fall("txd", "UART_TX", 0)
        then rise.rise == true
        and fall.rise == false

    test fsm_points_creation
        given s = fsm_state("uart_ctrl", "IDLE")
        and t = fsm_transition("uart_ctrl", "IDLE", "SEND")
        then s.state_name == "IDLE"
        and t.from_state == "IDLE"
        and t.to_state == "SEND"
        and t.illegal == false

    test illegal_transition_creation
        given t = illegal_transition("uart_ctrl", "SEND", "IDLE")
        then t.illegal == true
        and t.hit_count == 0

    test mark_hit_counts
        given p0 = line_point("a", "M", 1)
        and p1 = mark_hit_line(p0)
        and p2 = mark_hit_line(p1)
        then p0.hit_count == 0
        and p2.hit_count == 2
        and is_covered_line(p0) == false
        and is_covered_line(p2) == true

    test line_coverage_percent_mixed
        given a = mark_hit_line(line_point("a", "M", 1))
        and b = mark_hit_line(line_point("b", "M", 2))
        and c = line_point("c", "M", 3)
        and d = line_point("d", "M", 4)
        then hit_lines([a, b, c, d], 4) == 2
        and line_coverage_percent([a, b, c, d], 4) == 50

    test line_coverage_percent_full
        given a = mark_hit_line(line_point("a", "M", 1))
        and b = mark_hit_line(line_point("b", "M", 2))
        then line_coverage_percent([a, b], 2) == 100

    test line_coverage_percent_empty
        then line_coverage_percent([], 0) == 0

    test toggle_coverage_percent
        given r = mark_hit_toggle(toggle_rise("txd", "UART_TX", 0))
        and f = toggle_fall("txd", "UART_TX", 0)
        and r1 = mark_hit_toggle(toggle_rise("rxd", "UART_RX", 1))
        and f1 = toggle_fall("rxd", "UART_RX", 1)
        then hit_toggles([r, f, r1, f1], 4) == 2
        and toggle_coverage_percent([r, f, r1, f1], 4) == 50

    test fsm_coverage_counts
        given idle = mark_hit_state(fsm_state("uart_ctrl", "IDLE"))
        and send = mark_hit_state(fsm_state("uart_ctrl", "SEND"))
        and done = fsm_state("uart_ctrl", "DONE")
        and t1 = mark_hit_transition(fsm_transition("uart_ctrl", "IDLE", "SEND"))
        and t2 = fsm_transition("uart_ctrl", "SEND", "DONE")
        then hit_states([idle, send, done], 3) == 2
        and hit_transitions([t1, t2], 2) == 1

    test illegal_hits_zero_when_cold
        given x = illegal_transition("uart_ctrl", "SEND", "IDLE")
        then illegal_hits([x], 1) == 0

    test illegal_hits_counts_when_warm
        given x = mark_hit_transition(illegal_transition("uart_ctrl", "SEND", "IDLE"))
        then illegal_hits([x], 1) == 1

    test fsm_report_totals
        given idle = mark_hit_state(fsm_state("uart_ctrl", "IDLE"))
        and send = fsm_state("uart_ctrl", "SEND")
        and t1 = mark_hit_transition(fsm_transition("uart_ctrl", "IDLE", "SEND"))
        and t2 = fsm_transition("uart_ctrl", "SEND", "IDLE")
        and r = fsm_report("uart_ctrl", [idle, send], 2, [t1, t2], 2)
        then r.points_total == 4
        and r.points_hit == 2
        and report_percent(r) == 50

    test report_percent_empty
        given r = CoverageReport{.name = "empty", .points_total = 0, .points_hit = 0, .cycles = 0}
        then report_percent(r) == 0

    test validate_line_ok
        given p = line_point("a", "M", 1)
        then validate_line(p) == 0

    test validate_line_empty_name
        given p = line_point("", "M", 1)
        then validate_line(p) > 0

    test validate_line_zero_line
        given p = line_point("a", "M", 0)
        then validate_line(p) > 0

    test validate_toggle_ok
        given p = toggle_rise("txd", "UART_TX", 0)
        then validate_toggle(p) == 0

    test validate_toggle_empty_module
        given p = toggle_rise("txd", "", 0)
        then validate_toggle(p) > 0

    test validate_state_ok
        given p = fsm_state("uart_ctrl", "IDLE")
        then validate_state(p) == 0

    test validate_state_empty
        given p = fsm_state("", "")
        then validate_state(p) > 0

    test validate_transition_ok
        given p = fsm_transition("uart_ctrl", "IDLE", "SEND")
        then validate_transition(p) == 0

    test validate_transition_empty_to
        given p = fsm_transition("uart_ctrl", "IDLE", "")
        then validate_transition(p) > 0

    test validate_report_ok
        given r = fsm_report("uart_ctrl", [], 0, [], 0)
        then validate_report(r) == 0

    test validate_report_hit_over_total
        given r = CoverageReport{.name = "bad", .points_total = 1, .points_hit = 2, .cycles = 0}
        then validate_report(r) > 0

    // === Invariants ===

    invariant line_percent_bounded
        given a = mark_hit_line(line_point("a", "M", 1))
        and b = line_point("b", "M", 2)
        assert line_coverage_percent([a, b], 2) <= 100

    invariant illegal_must_stay_cold
        given x = illegal_transition("uart_ctrl", "SEND", "IDLE")
        assert illegal_hits([x], 1) == 0

    invariant validate_non_negative
        given p = line_point("inv", "M", 1)
        assert validate_line(p) >= 0

    invariant report_percent_bounded
        given r = fsm_report("inv", [], 0, [], 0)
        assert report_percent(r) <= 100

    // === Benchmarks ===

    bench line_percent_latency
        measure: nanoseconds for line_coverage_percent([], 0)
        target: < 50ns
}

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

Open the lesson's spec in the player ↗

All lessons