Константы с поворотом
parameter - это константа: компилятор подбирает её значение и зашивает в проект до старта симуляции. Поворот в том, что тот, кто инстанцирует module, может изменить значение. Один и тот же исходник может производить схемы разных размеров в зависимости от того, как вызывающие его параметризуют.
Так строят переиспользуемые IP. Module FIFO с parameters WIDTH и DEPTH может обслуживать каждую команду в компании. Счётчик с parameter WIDTH - один и тот же исходник, считает ли он до 16 или до 4 миллиардов.
Базовое объявление parameter
Три куска нового синтаксиса:
- Блок parameters:
#( parameter WIDTH = 8 )между именем module и port list. Дефолтное значение8используется, если никто не override-ит. - Место использования:
output reg [WIDTH-1:0] count. Parameter - это просто константа, мы вставляем её в bit range. Один и тот же исходник module даёт 8-битный или 16-битный выход в зависимости отWIDTH. - Синтаксис 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-блоков.