Menu

Ports module в Verilog: input, output и inout с примерами

Как объявлять ports module - input, output и inout - ANSI-стиль port list и когда output должен быть wire, а когда reg.

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

Интерфейс 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)), чтобы не зависеть от порядка.

Coddy programming languages illustration

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

НАЧАТЬ