Menu

Parameters в Verilog: делаем модули настраиваемыми

Как использовать parameter и localparam для задания compile-time констант, параметризации ширин и глубин и override значений при instantiation module.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Константы с поворотом

parameter - это константа: компилятор подбирает её значение и зашивает в проект до старта симуляции. Поворот в том, что тот, кто инстанцирует module, может изменить значение. Один и тот же исходник может производить схемы разных размеров в зависимости от того, как вызывающие его параметризуют.

Так строят переиспользуемые IP. Module FIFO с parameters WIDTH и DEPTH может обслуживать каждую команду в компании. Счётчик с parameter WIDTH - один и тот же исходник, считает ли он до 16 или до 4 миллиардов.

Базовое объявление parameter

Три куска нового синтаксиса:

  1. Блок parameters: #( parameter WIDTH = 8 ) между именем module и port list. Дефолтное значение 8 используется, если никто не override-ит.
  2. Место использования: output reg [WIDTH-1:0] count. Parameter - это просто константа, мы вставляем её в bit range. Один и тот же исходник module даёт 8-битный или 16-битный выход в зависимости от WIDTH.
  3. Синтаксис override: counter #(.WIDTH(16)) dut16(...) в момент instantiation. Блок #(...) идёт перед именем instance, после имени module. Не упомянутые parameters держат дефолты.

localparam: внутренние константы

Есть константы, которые ты не хочешь, чтобы вызывающие переопределяли. Классический случай - кодировки состояний:

localparam делает IDLE, RUNNING, DONE доступными внутри module, но не переопределяемыми с instantiation. Это правильный выбор - менять, какое значение состояния означает IDLE, снаружи module было бы ужасно.

Используй parameter для вещей, которые вызывающие должны конфигурировать (ширины, глубины, опции поведения), и localparam для всего остального. Частый паттерн - выводить localparam из parameter:

parameter WIDTH = 32;
localparam WIDTH_M1 = WIDTH - 1;   // вычисляется один раз, не override-ится

Параметризованные ширины на практике

Самое частое использование parameters - гибкие ширины шин. Вот adder, который работает на любой ширине:

Один исходный файл, два instance, две разные ширины, никакого copy-paste. Это вся выгода parameters.

Несколько parameters, override по имени

В более крупных модулях часто будет несколько parameters. Override-ь их по имени в любом порядке:

module fifo #(
    parameter WIDTH = 8,
    parameter DEPTH = 16,
    parameter AFULL = DEPTH - 2
)(
    input  wire             clk,
    input  wire             reset,
    // ... ports ...
);
    // ...
endmodule

// На месте вызова:
fifo #(.WIDTH(32), .DEPTH(1024)) cmd_queue (.clk(clk), .reset(reset), ...);

Не обязательно override-ить каждый parameter; не упомянутые держат дефолты. Дефолт AFULL в примере вычисляется из DEPTH, что значит: если override-ишь DEPTH, AFULL подтянется автоматически - именно такое поведение даёт и localparam, если ты не хочешь, чтобы вызывающие могли override-ить AFULL независимо.

Типичные ошибки

Забыли # в override. counter (.WIDTH(16)) dut(...) выглядит как override, но Verilog читает (.WIDTH(16)) как port-соединение. Нужно counter #(.WIDTH(16)) dut(...).

Использование parameters там, где они недостаточно константны. Parameters резолвятся в момент elaboration, до того как существуют какие-либо сигналы. Нельзя параметризовать на runtime-сигнал - если значение зависит от того, что делает input на момент симуляции, это не parameter, это логика.

Путают parameter и localparam в port list. В блок #(...) наверху идёт только parameter. localparam живёт внутри тела. Компилятор скажет, если перепутаешь.

Что дальше

Теперь умеешь делать модули, размер которых задаётся в compile time. Следующие два документа разбирают правила записи литеральных констант (8'h1F, 4'b1010, 32'd100) и значения x и z, которые появляются, когда сигналы не приводятся или конфликтуют. После этого переходим к операторам - всему, что можно делать с векторами и parameters, которые ты теперь увидел.

Часто задаваемые вопросы

Что такое parameter в Verilog?

parameter - это compile-time-константа, объявленная внутри module. Её можно использовать везде, где допустима константа: ширины шин, глубины memory, кодировки состояний, дефолтные значения. Главное - каждый instance module может переопределить parameter, так что один и тот же исходник может производить, скажем, 8-битный счётчик в одном месте и 32-битный - в другом.

В чём разница между parameter и localparam в Verilog?

Значения parameter можно переопределить снаружи module при instantiation. Значения localparam - нельзя, это только внутренние константы. Используй localparam для кодировок состояний и производных констант, в которые автор module не хочет, чтобы вызывающие лезли.

Как переопределить parameter в Verilog?

При instantiation module добавь блок override перед именем instance: counter #(.WIDTH(16)) my_inst (.clk(clk), .count(count));. Синтаксис .WIDTH(16) задаёт конкретный parameter; не упомянутые держат дефолты. Несколько override разделяются запятыми внутри #(...).

Что такое параметризованный module?

Module, который выставляет один или несколько parameter, переопределяемых вызывающими. Параметризованный FIFO может иметь parameters WIDTH и DEPTH, чтобы один исходник производил 32-битный-на-16-глубокий FIFO в одном месте и 8-битный-на-1024-глубокий - в другом. Так пишут библиотеки переиспользуемых IP-блоков.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ