t27.aiEnglish

Анатомия тестбенча

Вы научитесь

Из чего состоит каждый *_tb.t27: стимулы, ожидания, проверки, вердикт.

Каждый тестбенч в этом репозитории — один .t27-файл с одним и тем же скелетом, и плеер компилирует его как любой другой spec. Откройте fifo_tb: он строит стимулы (байты в FIFO), ожидания (те же байты по порядку), проверки (по одному ассерту на инвариант, чтобы провал называл именно нарушенный) и вердикт (счёт, а не ощущение). Виджет генерирует JavaScript из spec прямо в браузере и запускает его — та же связка «сгенерировать и исполнить», на которой стоит тестбенч, причём достаточно маленькая, чтобы увидеть её целиком.

Попробуйте

Сгенерируйте JavaScript для spec fifo_tb в плеере и запустите его; найдите ассерт, который упал бы раньше остальных, если бы FIFO терял байт.

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

hello_world.t27 to JavaScript, in a terminal inside the post
hello_world.t27 to JavaScript, in a terminal inside the post ↗

A terminal in your browser running the t27 compiler as WebAssembly: t27c gen js prints the JavaScript it emits for hello_world.t27, and t27c test runs the spec's tests.

specs/fpga/testbench/fifo_tb.t27

// FIFO testbench spec
// This spec exercises a small synchronous FIFO with a power-of-two depth.
// It provides write/read helpers, a combined transaction function,
// and a set of tests covering basic operation, edge cases, and invariants.

const DEPTH : u32 = 8;

struct Fifo {
    mem : [DEPTH]u32,
    head : u32,
    tail : u32,
    fill_count : u32,
}

var fifo : Fifo = Fifo {
    mem = [0, 0, 0, 0, 0, 0, 0, 0],
    head = 0,
    tail = 0,
    fill_count = 0,
};

var wr_en : bool = false;
var rd_en : bool = false;
var wr_data : u32 = 0;
var rd_data : u32 = 0;

fn reset() {
    fifo.head = 0;
    fifo.tail = 0;
    fifo.fill_count = 0;
    wr_en = false;
    rd_en = false;
    wr_data = 0;
    rd_data = 0;
}

fn tick() {
    if wr_en && !rd_en && !full() {
        fifo.mem[fifo.tail] = wr_data;
        fifo.tail = (fifo.tail + 1) % DEPTH;
        fifo.fill_count = fifo.fill_count + 1;
    } else if rd_en && !wr_en && !empty() {
        rd_data = fifo.mem[fifo.head];
        fifo.head = (fifo.head + 1) % DEPTH;
        fifo.fill_count = fifo.fill_count - 1;
    } else if wr_en && rd_en {
        if !full() && !empty() {
            rd_data = fifo.mem[fifo.head];
            fifo.mem[fifo.tail] = wr_data;
            fifo.head = (fifo.head + 1) % DEPTH;
            fifo.tail = (fifo.tail + 1) % DEPTH;
        } else if !full() && empty() {
            fifo.mem[fifo.tail] = wr_data;
            fifo.tail = (fifo.tail + 1) % DEPTH;
            fifo.fill_count = fifo.fill_count + 1;
        } else if full() && !empty() {
            rd_data = fifo.mem[fifo.head];
            fifo.head = (fifo.head + 1) % DEPTH;
            fifo.fill_count = fifo.fill_count - 1;
        }
    }
    wr_en = false;
    rd_en = false;
}

fn full() -> bool {
    return fifo.fill_count == DEPTH;
}

fn empty() -> bool {
    return fifo.fill_count == 0;
}

fn write_word(data : u32) -> bool {
    if full() {
        return false;
    }
    wr_en = true;
    wr_data = data;
    tick();
    wr_en = false;
    return true;
}

fn read_word() -> u32 {
    if empty() {
        return 0xFFFF;
    }
    rd_en = true;
    tick();
    rd_en = false;
    return rd_data;
}

fn on_comb(data: u32) -> bool {
    return write_word(data);
}

test test_basic_write {
    reset();
    var ok = write_word(0xDEAD);
    assert ok == true;
    assert fifo.fill_count == 1;
}

test test_basic_read {
    reset();
    write_word(0xBEEF);
    var data = read_word();
    assert data == 0xBEEF;
    assert fifo.fill_count == 0;
}

test test_full_after_writes {
    reset();
    var i = 0;
    while i < DEPTH {
        var ok = write_word(i);
        assert ok == true;
        i = i + 1;
    }
    assert full() == true;
    var ok = write_word(0xFFFF);
    assert ok == false;
}

test test_empty_after_read {
    reset();
    write_word(0x1234);
    write_word(0x5678);
    var d1 = read_word();
    var d2 = read_word();
    assert d1 == 0x1234;
    assert d2 == 0x5678;
    assert empty() == true;
}

test test_overflow_rejected {
    reset();
    var i = 0;
    while i < DEPTH {
        write_word(i);
        i = i + 1;
    }
    var ok = write_word(0xABCD);
    assert ok == false;
    assert fifo.fill_count == DEPTH;
}

test test_underflow_returns_sentinel {
    reset();
    var data = read_word();
    assert data == 0xFFFF;
    assert fifo.fill_count == 0;
}

test test_overflow_then_underflow {
    reset();
    var i = 0;
    while i < DEPTH {
        write_word(i);
        i = i + 1;
    }
    var extra = write_word(0xFFFF);
    assert extra == false;
    var j = 0;
    while j < DEPTH {
        var data = read_word();
        assert data == j;
        j = j + 1;
    }
    var sentinel = read_word();
    assert sentinel == 0xFFFF;
}

test test_rapid_alternating {
    reset();
    var i = 0;
    while i < 20 {
        var ok = write_word(i);
        assert ok == true;
        var data = read_word();
        assert data == i;
        i = i + 1;
    }
    assert empty() == true;
}

test test_wrap_behavior {
    reset();
    var i = 0;
    while i < 16 {
        write_word(i);
        i = i + 1;
    }
    var j = 0;
    while j < 8 {
        var data = read_word();
        assert data == (j + 8);
        j = j + 1;
    }
    assert fifo.fill_count == 8;
}

test test_simultaneous_rw {
    reset();
    write_word(0x42);
    wr_en = true;
    rd_en = true;
    wr_data = 0x43;
    tick();
    wr_en = false;
    rd_en = false;
    invariant fill_count == 1;
}

test test_on_comb_writes_when_not_full {
    reset();
    var result = on_comb(0xAAAA);
    assert result == true;
    assert fifo.fill_count == 1;
    assert fifo.mem[0] == 0xAAAA;
}

test test_on_comb_rejects_when_full {
    reset();
    var i = 0;
    while i < DEPTH {
        write_word(i);
        i = i + 1;
    }
    var result = on_comb(0xBBBB);
    assert result == false;
    assert fifo.fill_count == DEPTH;
}

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

Все уроки