Интерфейс module
Port list module - это его интерфейс: всё, что внешний мир может увидеть и потрогать. Внутри module тело вычисляет выходы из входов. Ports - это провода, пересекающие границу.
Современный ANSI-стиль кладёт направление, тип и ширину прямо в одну строку:
module my_module(
input wire clk,
input wire reset,
input wire [7:0] data_in,
output reg [7:0] data_out,
output wire valid
);
// тело
endmodule
Это вся форма. У каждого port есть:
- Направление:
input,outputилиinout. - Тип:
wireилиreg(илиlogicв SystemVerilog). - Ширина: диапазон вроде
[7:0]или однобитный, если опущена. - Имя: идентификатор, который ты будешь использовать в теле.
Три направления
input - приводится в действие снаружи
Inputs всегда wire. Driver - снаружи module: либо сигнал модуля выше, либо reg из testbench. Внутри этого module можно читать inputs в выражениях, но никогда не присваивать им:
module reader(input wire [7:0] data);
initial $display("data = %h", data);
endmodule
Запись data = ... внутри module была бы ошибкой.
output - приводится в действие изнутри
Outputs приводятся чем-то внутри этого module. Они могут быть wire (приводятся через assign или выход подмодуля) или reg (приводятся из always/initial).
module driver(
input wire a,
input wire b,
input wire clk,
output wire y, // wire - приводится через assign
output reg q // reg - приводится через always
);
assign y = a & b; // OK, потому что y - wire
always @(posedge clk)
q <= a; // OK, потому что q - reg
endmodule
Попытка присвоить y внутри always-блока или приводить q через assign - ошибка компиляции. Выбор ключевого слова должен соответствовать driver.
inout - двунаправленный
inout-ports - это wires, которые могут приводиться в действие либо module, либо тем, что снаружи, попеременно. Они появляются на границах чипа - линии SDA для I²C, двунаправленные GPIO-pins, общие data-шины. Внутри module ты управляешь направлением через tri-state-паттерн:
module bidir_pin(
input wire data_out,
input wire output_enable,
output wire data_in,
inout wire pin
);
assign pin = output_enable ? data_out : 1'bz;
assign data_in = pin;
endmodule
pin - общий wire. Когда output_enable high, module приводит pin в data_out. Когда low - отпускает pin в high-impedance (z), позволяя внешнему driver использовать его. data_in всегда наблюдает, что сейчас на pin.
inout редок во внутренне-чисто-логических блоках. Если строишь контроллер памяти, ядро CPU, image-processing-pipeline - может вообще не понадобиться. Это фича для I/O-ring на границах чипа.
Однобитные vs многобитные ports
Диапазон ширины опционален - опусти его для однобитного port:
input wire valid, // 1 бит
input wire [7:0] data, // 8 бит
input wire [31:0] addr, // 32 бита
Для ширин можно использовать parameters - так работают параметризованные модули:
module bus #(
parameter WIDTH = 32
)(
input wire [WIDTH-1:0] in,
output wire [WIDTH-1:0] out
);
assign out = in;
endmodule
ANSI vs Verilog-1995
Иногда увидишь старый код, в котором port list и объявления разделены:
// Старый стиль Verilog-1995 - НЕ делай так в новом коде
module foo(clk, data_in, data_out);
input clk;
input [7:0] data_in;
output reg [7:0] data_out;
// ...
endmodule
Port list содержит только имена; каждый port затем объявлен отдельно внутри тела. Многословно, склонно к ошибкам "назван в port list, но не объявлен" и в два раза больше печатать. ANSI-2001 (тот, что показан во всех этих документах) заменяет оба:
module foo(
input wire clk,
input wire [7:0] data_in,
output reg [7:0] data_out
);
// ...
endmodule
Используй ANSI-стиль. Тулы поддерживают его с 2001 года.
Полный пример
В этом одном module есть:
- Parameter (
WIDTH). - Inputs для clock, reset, load enable, data и shift enable.
reg-выход (data_out), приводимый внутри блокаalways.wire-выход (msb_out), приводимый непрерывным присваиванием.- Полное ANSI-объявление, в котором направление, тип и ширина каждого port прописаны inline.
Так формируется почти каждый синтезируемый module, который ты напишешь.
Что дальше
Следующий документ - Module Instantiation - показывает, как взять этот module и использовать его внутри более крупного проекта. Shifter, который ты только что написал, сам по себе бесполезен; он окупается, когда что-то его инстанцирует и встраивает в систему.
Часто задаваемые вопросы
Какие три направления ports есть в Verilog?
input, output и inout. input приводится в действие снаружи module. output приводится в действие изнутри module. inout двунаправленный - пригодится для tri-state шин, где module и пишет в provod, и читает с него. Подавляющее большинство внутренних ports - input или output; inout появляется только на pin'ах чипа.
В чём разница между output wire и output reg?
output wire значит, что выход приводится непрерывным присваиванием или выходом подмодуля - всем, что вне always. output reg значит, что он приводится из процедурного блока (initial или always). Ключевое слово управляет тем, можешь ли ты целиться в сигнал из always, а не тем, содержит ли синтезируемое железо flip-flop.
Что такое ANSI-стиль ports в Verilog?
ANSI-стиль объявляет направление и тип каждого port прямо в port list: module foo(input wire [7:0] data, output reg [7:0] result);. Старый стиль Verilog-1995 перечислял в port list только имена и переобъявлял их внутри module. В новом коде всегда используй ANSI - короче, меньше ошибок и это стандарт везде, где есть современный Verilog.
Может ли module иметь много входов и выходов?
Да - у module может быть сколько угодно ports в любой комбинации направлений. Раздели их запятыми в port list. Порядок в port list определяет позиционный порядок при instantiation, но почти всегда используют именованные соединения (.port(signal)), чтобы не зависеть от порядка.