Параметры и обобщённые модули: parameter и generate

Учимся писать модули, которые настраиваются под нужный размер, и размножать аппаратуру по шаблону.

parameter — настраиваемая константа модуля, задаваемая при инстанцировании; позволяет описать один модуль, работающий с любой разрядностью.

Писать отдельный модуль под 8-битный счётчик, потом под 16-битный, потом под 32-битный — расточительно. Verilog даёт два инструмента обобщения: parameter (настраиваемые константы) и generate (повторение аппаратуры по шаблону). С ними один модуль покрывает целое семейство схем — это и переиспользование, и защита от ошибок копипасты.

Параметры: настраиваемая разрядность

parameter объявляет константу со значением по умолчанию, которое можно переопределить при инстанцировании. Классический пример — счётчик любой разрядности:

module counter #(
    parameter WIDTH = 8          // разрядность по умолчанию — 8 бит
)(
    input  wire             clk,
    input  wire             rst,
    output reg  [WIDTH-1:0] count
);
    always @(posedge clk) begin
        if (rst) count <= 0;
        else     count <= count + 1;
    end
endmodule

Теперь один и тот же модуль инстанцируется под разные размеры:

counter #(.WIDTH(8))  c8  (.clk(clk), .rst(rst), .count(cnt8));   // 8-битный
counter #(.WIDTH(16)) c16 (.clk(clk), .rst(rst), .count(cnt16));  // 16-битный
counter #(.WIDTH(32)) c32 (.clk(clk), .rst(rst), .count(cnt32));  // 32-битный

Один исходник — три разных счётчика в железе. Изменили логику — она исправилась во всех инстансах сразу.

generate: размножение аппаратуры

Блок generate с переменной genvar повторяет фрагмент схемы N раз во время синтеза. Это не цикл во времени — это шаблон, по которому строится N копий железа. Пример: 8-битный побитовый XOR двух шин как восемь однобитных вентилей:

genvar i;
generate
    for (i = 0; i < 8; i = i + 1) begin : xor_gen
        assign y[i] = a[i] ^ b[i];   // создаёт 8 независимых вентилей XOR
    end
endgenerate

После синтеза появятся 8 физических вентилей, работающих параллельно. generate особенно полезен для регулярных структур: массивов сумматоров, цепочек регистров, матриц обработки.

Как работает под капотом

Параметр меняет только разрядность, а логика остаётся той же. Промоделируем счётчик разной разрядности на Python и увидим, как параметр WIDTH задаёт предел переполнения:

def max_count(width):
    return 2 ** width - 1     # максимальное значение WIDTH-битного счётчика

for width in [4, 8, 16, 32]:
    print(f"WIDTH={width:2} -> счёт 0..{max_count(width)} ({2**width} значений)")

Вывод:

WIDTH= 4 -> счёт 0..15 (16 значений)
WIDTH= 8 -> счёт 0..255 (256 значений)
WIDTH=16 -> счёт 0..65535 (65536 значений)
WIDTH=32 -> счёт 0..4294967295 (4294967296 значений)

Один параметр WIDTH определяет и число триггеров, и предел счёта. Это и есть обобщение: меняем константу — получаем нужный размер железа без переписывания логики.

Частые ошибки

  • Считать generate for циклом выполнения. Это шаблон размножения железа на этапе синтеза, а не цикл во времени; внутри не может быть «итераций по такту».
  • Жёстко зашивать разрядность. Магические числа вроде [7:0] по всему коду затрудняют переиспользование; выносите их в parameter.
  • Забыть метку begin : имя в generate-цикле. Многие инструменты требуют именованный блок внутри generate.

Итог

  • parameter делает модуль настраиваемым (например, по разрядности) и переиспользуемым.
  • Переопределение при инстанцировании: module #(.WIDTH(16)) inst (...).
  • generate с genvar размножает аппаратуру по шаблону на этапе синтеза.
  • generate-цикл строит N копий железа, а не выполняет итерации во времени.
Проверьте себя
1. Что позволяет parameter в модуле Verilog?
AСоздавать переменные во время выполнения
BЗадавать настраиваемую константу (например, разрядность), переопределяемую при инстанцировании
CУскорять тактовую частоту
DПодключать внешнюю память
2. Что делает блок generate с genvar?
AВыполняет цикл во времени по тактам
BРазмножает фрагмент аппаратуры по шаблону на этапе синтеза, создавая N копий железа
CГенерирует тактовый сигнал
DСоздаёт случайные значения
3. Как инстанцировать модуль counter с разрядностью 16 бит?
Acounter(16) c16 (...)
Bcounter #(.WIDTH(16)) c16 (...)
Ccounter c16 [16] (...)
Dcounter.WIDTH = 16