LSSK · BreadboardEnglish

Правила стиля SystemVerilog

Практические правила по изученным учебным исходникам Basics-Graphics-Music и systemverilog-homework. Это анализ примеров, а не официальный единый стандарт их авторов. Для breadboard/ выбран согласованный вариант с отступом в четыре пробела и дополнительными требованиями yuri_rules_3.txt. Явные требования пользователя имеют приоритет над исходными примерами. Обновлено 21 октября 2026 года.

Markdown

1. Четыре пробела; без табуляции

Каждый вложенный блок сдвигайте на четыре пробела. Для выравнивания используйте пробелы; удаляйте пробелы в конце строки. Четыре пробела — явное требование пользователя для breadboard/. В исходных репозиториях встречаются отступы и в два, и в четыре пробела.

2. Оформление модулей и блоков

Имя модуля, список параметров с # ( и список портов начинайте с отдельных строк. begin и end пишите на отдельных строках; else — с новой строки после end. Для одного присваивания begin/end можно опустить. Короткие ветви case оставляйте компактными; метки выравнивайте с case, как в примерах автоматов BGM и формул Homework.

3. Пробелы в выражениях

Пишите if (condition), case (state), always_ff @ (posedge clk), function_name (arg), state [i], width' (value) и for (int i = 0; i < n; i ++). Отделяйте пробелами двоичные операции и операнд унарной операции: a | b, ~ a, | state. Конкатенации: { state [8:0], state [9] }. Не разделяйте символы операций <= и !==.

4. Содержательные имена

Для модулей и сигналов используйте lower_snake_case. Сохраняйте принятые имена интерфейсов: clk, rst, w_led, arg_vld. В изученных примерах состояния называются S0/S1, STATE_START и st_idle; выбирайте одну систему внутри модуля. Аналогично выбирайте next_state, new_state или d_state. В этом уроке используется next_state; публичные параметры оболочек сохраняют свои имена.

5. Читаемые объявления и порты

Используйте объявления портов ANSI. Для портов-цепей пишите input или output без wire; для процедурных выходов сохраняйте output logic. Разделяйте такт/сброс, данные, управление и выходы пустыми строками или короткими комментариями. Выравнивайте направления, типы и диапазоны по столбцам, а все имена портов начинайте в одном столбце. Используйте пробелы, не табуляцию; каждый порт — на отдельной строке. Пример ниже соответствует оформлению, заданному пользователем.

6. wire — только с непрерывным присваиванием в объявлении

Используйте wire тогда и только тогда, когда объявление содержит непрерывное присваивание: wire a = expression;. В остальных объявлениях внутренних сигналов, соединений модулей и сигналов тестбенча, включая выходы DUT, используйте logic. Сохраняйте отдельно заданное оформление портов: input/output без wire и output logic для процедурных выходов. wire с = непрерывно вычисляет выражение; logic a = expression — однократная инициализация переменной, которая не заменяет комбинационное уравнение. Это явное правило пользователя имеет приоритет над объявлениями wire без присваивания из некоторых исходных примеров.

7. Обновление состояния: always_ff и <=

Для синтезируемого состояния, меняющегося по фронту, используйте always_ff и неблокирующие присваивания (<=). У каждой переменной состояния должен быть один источник записи. Обычный always допустим для генератора такта в тестбенче. В новом SystemVerilog используйте logic вместо устаревшего reg.

8. Комбинационная логика: assign или always_comb

Для простого уравнения используйте assign или объявление wire с непрерывным присваиванием. Для дерева решений или дешифратора — always_comb с блокирующими присваиваниями (=). Задавайте значения по умолчанию или охватывайте все ветви для каждого выхода. next_state = state задаёт удержание состояния. Это предотвращает защёлку, но не восстанавливает автоматически недопустимое состояние.

9. Сброс соответствует контракту схемы

Не меняйте семантику сброса ради оформления. В примерах улитки и счётчика BGM сброс асинхронный, активный единицей; в изученных FSM/Fibonacci Homework — синхронный, тоже активный единицей. Здесь rst остаётся асинхронным и активным единицей, как RESET CD4017. rst_n Tiny Tapeout инвертируется на границе оболочки. Единого вида сброса для двух репозиториев нет.

10. Явно показывайте структуру автомата

Разделяйте регистр состояния, логику следующего состояния и выходную логику комментариями и пустыми строками. В исходниках есть enum и варианты с localparam/one-hot. Для простого циклического унитарного автомата уравнение вращения яснее десяти однотипных ветвей case. Согласуйте граф, сброс, условия переходов и дешифрацию. У этого автомата Мура значения выходов указываются внутри вершин.

11. Сохраняйте кодирование и поведение

Имена состояний, физические выводы и кодовые векторы — разные понятия. Не заменяйте внешний десятибитный one-hot выход двоичным enum под видом изменения стиля. В исходниках встречаются enum с bit и с logic; здесь используется четырёхзначный logic, чтобы неопределённое состояние было видно в симуляции. Изменение стиля не добавляет восстановление из недопустимых состояний.

12. Осознанно задавайте разрядности

Когда важна разрядность, задавайте диапазоны и размерные литералы; для заполнения приёмника используйте '0 и '1. По необходимости применяйте параметры, $clog2 и явные приведения, например 8' (i). Учитывайте знаковость. В учебных исходниках встречаются безразмерные константы; это не требование полагаться на неявное расширение.

13. Подключайте модули по именам портов

Используйте явные выровненные подключения .port (signal). В обоих репозиториях встречаются сокращённые подключения и .* при совпадении имён. В этом уроке явные подключения помогают проверять модели выводов и оболочки плат; прежние позиционные вызовы заменены. Блокам generate давайте содержательные имена.

14. Разделяйте действия и поясняйте замысел

Пишите по одному процедурному действию на строке, кроме короткой ветви case или заголовка цикла. Разделяйте объявления, экземпляры, комбинационную логику, регистры и проверки пустыми строками. Комментарии поясняют аппаратный смысл или неочевидное решение; в длинных модулях допустимы разделители из дефисов. Общие комментарии HDL — на английском; текст урока переведён на русский.

15. Тестбенчи читаемы и проверяют результат

Используйте logic, int, именованные подключения DUT, процедурные стимулы и независимо рассчитанный ожидаемый результат. Задержки и системные задачи оставляйте в симуляционном коде. Когда неопределённость должна означать ошибку, сравнивайте через !==. Печатайте PASS после всех проверок. Homework показывает такой подход; в breadboard/ ошибки дополнительно завершаются через $fatal (1, ...), чтобы скрипт получил ненулевой код.

16. Обосновывайте особенности инструментов

Не копируйте include конфигурации, подавление предупреждений или подсказки синтезатору без понимания цели. Объясняйте подавления и ограничивайте область действия. BGM использует config.svh для своей инфраструктуры; автономным модулям урока он не нужен. Для обычных портов input/output сохраняйте стандартный тип цепей по умолчанию. Не применяйте default_nettype none к этим заголовкам: Questa отвергает их неявные типы цепей. Поэтому автономные файлы не содержат эту директиву, а внутренние цепи объявлены явно.

17. Объявляйте единицы времени внутри каждого модуля

Используйте современные объявления SystemVerilog: timeunit 1ns; и затем timeprecision 1ps;. Размещайте их сразу после заголовка модуля, перед остальными элементами, с отступом в четыре пробела. Это относится к RTL, моделям микросхем, оболочкам и тестбенчам. Не используйте устаревшую директиву timescale с обратной кавычкой. Это явное требование пользователя для breadboard/, а не утверждение обо всех исходных файлах репозиториев. Сохраняются единица задержек в наносекунду и точность в пикосекунду; параметры времени теперь локальны для каждого модуля. Частоту такта эти объявления не задают.

18. Выравнивайте знаки присваивания

Выравнивайте = по вертикали в каждой последовательности присваиваний, включая присваивания в объявлениях, параметры и расчёт ожидаемых значений. Аналогично выравнивайте <= в связанных обновлениях регистров или стимулах. Используйте пробелы и одно присваивание на строку; не объединяйте несвязанные блоки в один широкий столбец.

19. Выделяйте генератор такта тестбенча

Объявляйте logic clk = 1'b0; и формируйте такт через always # (period / 2) clk = ~ clk;. Задавайте period в единицах времени модуля. Этот генератор — единственный процедурный источник clk. Не переключайте такт блокирующими присваиваниями внутри initial со стимулами или внутри задачи стимулов. Заданный пользователем генератор имеет приоритет над прежними вариантами initial/forever из исходных репозиториев.

20. Синхронизируйтесь по событиям; обосновывайте задержки и спады

Обычно используйте @ (posedge clk). Добавляйте # 1 только при необходимости увидеть обновление регистров и установление комбинационных выходов либо проверить асинхронный вход до следующего такта. Не заменяйте ожидание тактового события произвольной задержкой. Не задавайте и не считывайте сигналы по negedge, если проверяемое поведение этого не требует. Избегайте гонок тестбенча с DUT: задавайте стимулы неблокирующими присваиваниями по фронту либо меняйте входы после установления и проверки выходов. Здесь выбран второй вариант. Все особые случаи объяснены ниже. Если требование невозможно выполнить, создайте need_to_deviate_from_style.html с указанием кода, причины и альтернатив.

21. Используйте тела задач SystemVerilog

После объявления task пишите непосредственно локальные объявления и операторы, затем endtask. Не оборачивайте всё тело в избыточную пару begin/end в стиле Verilog-95. Сохраняйте begin/end там, где они нужны внутри цикла, условия или другого вложенного блока. Для перекрывающихся вызовов используйте automatic; здесь задачи вызываются последовательно.

22. Задавайте тайм-ауты в тактах

Используйте отдельный initial с repeat (timeout_cycles) @ (posedge clk);, после которого вызывается $fatal (1, ...). Предел должен превышать длину штатного сценария. Не задавайте сторожевой тайм-аут абсолютной задержкой #. Во всех тестбенчах есть тайм-аут в тактах: 500 для основного теста и CMOS, 200 для оболочек.

Пример из этого урока

// Synthesizable generic FSM. The state output uses one-hot encoding.
// Reset is asynchronous and active high; it selects S0.

module traffic_light
(
    input              clk,
    input              rst,

    output             red,
    output             yellow,
    output             green,
    output logic [9:0] state
);

    timeunit 1ns;
    timeprecision 1ps;

    wire [9:0] next_state = { state [8:0], state [9] };

    // State register

    always_ff @ (posedge clk or posedge rst)
        if (rst)
            state <= 10'b00_0000_0001;
        else
            state <= next_state;

    // Moore outputs depend only on the current state.

    assign red    = | state [3:0];
    assign yellow = | state [5:4];
    assign green  = | state [9:6];

endmodule

Фрагменты тестбенча

Фрагменты из основного тестбенча; DUT, расчёт ожидаемого результата и остальные проверки находятся в полном исходнике. tb_traffic_light.sv

    timeunit 1ns;
    timeprecision 1ps;

    localparam period         = 20;
    localparam timeout_cycles = 500;

    logic clk = 1'b0;
    logic rst = 1'b0;

    always # (period / 2) clk = ~ clk;

    task tick;
        @ (posedge clk);
        expected = rst ? 0 : (expected + 1) % 10;
        # 1;  // Let register updates and lamp decoding settle before sampling.
        check_state ();
    endtask

    initial
    begin
        repeat (timeout_cycles) @ (posedge clk);
        $fatal (1, "Timeout after %0d clock cycles", timeout_cycles);
    end

Почему остаются отдельные # 1 и negedge

Основной тест проверяет сброс из каждого состояния при высоком и низком CLOCK. Ветвь низкого уровня ждёт negedge, через # 1 проверяет отсутствие изменения по спаду, затем проверяет асинхронный сброс до следующего фронта. Проверка сброса только по posedge не доказывала бы это поведение.

Тест CMOS снимает INHIBIT при низком CLOCK после negedge и требует отсутствия счёта. Отдельно он снимает INHIBIT при высоком CLOCK и требует инкремента. Это разные условия тактирования CD4017; стимулы только по фронтам CLOCK исключили бы проверку низкого уровня.

Остальные # 1 дают завершиться неблокирующим обновлениям регистров и дешифрации, размещают начальный сброс после тактового события без гонки либо позволяют проверить асинхронные reset/inhibit и разрешение выходов до следующего такта. Затем входы меняются между фронтами. Период равен 20 нс; самая длинная последовательность при высоком CLOCK завершается до конца полупериода в 10 нс. Эти задержки управляют порядком событий симуляции и не моделируют задержки микросхем.

Это необходимые случаи, прямо разрешённые оговорками yuri_rules_3.txt. Отказываться от требований не потребовалось, поэтому отчёт об отклонениях не нужен. Нет ручного формирования тактовых импульсов, произвольных задержек стимулов и тайм-аутов в абсолютном времени.

Изученные исходники

SHA-256 / provenance