Menu

Verilog 模块端口:input、output 与 inout 详解

如何声明模块端口(input、output 和 inout),ANSI 风格的端口列表,以及输出端口什么时候该用 wire、什么时候该用 reg。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

模块的接口

模块的端口列表就是它的接口:外部世界能看到和接触到的一切。在模块内部,模块体根据输入计算输出。端口就是穿过这条边界的导线。

现代的 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
);
    // body
endmodule

整体结构就是这样。每个端口都有:

  • 一个方向:input、output 或 inout。
  • 一个类型:wire 或 reg(在 SystemVerilog 中还可以是 logic)。
  • 一个位宽:像 [7:0] 这样的范围,省略则为单个位。
  • 一个名字:在模块体内使用的标识符。

三种方向

input:由外部驱动

输入总是 wire。驱动源在模块外部:要么是上层模块的信号,要么是测试平台中的 reg。在这个模块内部,你可以在表达式中读取输入,但永远不能给它们赋值:

module reader(input wire [7:0] data);
    initial $display("data = %h", data);
endmodule

在模块内部写 data = ... 会报错。

output:由内部驱动

输出由这个模块内部的某个东西驱动。它可以是 wire(由 assign 或子模块的输出驱动),也可以是 reg(由 always/initial 驱动)。

module driver(
    input  wire       a,
    input  wire       b,
    input  wire       clk,
    output wire       y,    // wire - driven by assign
    output reg        q     // reg  - driven by always
);
    assign y = a & b;       // OK because y is wire

    always @(posedge clk)
        q <= a;             // OK because q is reg
endmodule

在 always 块内给 y 赋值,或者用 assign 驱动 q,都会导致编译错误。关键字的选择必须和驱动方式相匹配。

inout:双向

inout 端口是一种导线,可以轮流由模块自身或外部连接的器件来驱动。它们出现在芯片的边界上:I²C 的 SDA 线、双向 GPIO 引脚、共享数据总线。在模块内部,用三态写法来控制方向:

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 是共享的导线。当 output_enable 为高时,模块把 pin 驱动为 data_out。为低时,模块把引脚释放为高阻态(z),让外部的驱动源使用它。data_in 始终观察引脚上当前的值。

在纯内部逻辑中很少用到 inout。如果你在做存储器控制器、CPU 核心或图像处理流水线,可能永远都用不上它。它是为芯片边界上的 I/O 环准备的功能。

单位端口与多位端口

位宽范围是可选的,省略它就得到一个单位端口:

input wire valid,         // 1 bit
input wire [7:0] data,    // 8 bits
input wire [31:0] addr,   // 32 bits

位宽可以使用参数,参数化模块就是这样实现的:

module bus #(
    parameter WIDTH = 32
)(
    input  wire [WIDTH-1:0] in,
    output wire [WIDTH-1:0] out
);
    assign out = in;
endmodule

ANSI 风格与 Verilog-1995 风格

偶尔你会看到一些旧代码,把端口列表和端口声明分开写:

// Old Verilog-1995 style - DON'T do this in new code
module foo(clk, data_in, data_out);
    input clk;
    input [7:0] data_in;
    output reg [7:0] data_out;
    // ...
endmodule

端口列表中只有名字,然后每个端口在模块体内单独声明。这种写法冗长,容易出现“端口列表里写了名字却没有声明”的错误,要敲的字也多了一倍。ANSI-2001 风格(这套文档中一直使用的写法)把两者合而为一:

module foo(
    input  wire       clk,
    input  wire [7:0] data_in,
    output reg  [7:0] data_out
);
    // ...
endmodule

请使用 ANSI 风格。工具从 2001 年起就支持它了。

完整示例

这一个模块包含了:

  • 一个参数(WIDTH)。
  • 时钟、复位、加载使能、数据和移位使能这几个输入。
  • 一个在 always 块内驱动的 reg 输出(data_out)。
  • 一个由连续赋值驱动的 wire 输出(msb_out)。
  • 一个完整的 ANSI 风格声明,在一行中列出每个端口的方向、类型和位宽。

你将来写的几乎每个可综合模块,都是这个样子。

接下来

下一篇文档 模块实例化 会展示如何把这个模块用到更大的设计中。你刚写的移位器单独存在时没什么用;只有当别的模块实例化它、把它接入系统时,它才能发挥作用。

常见问题

Verilog 中的三种端口方向是什么?

input、output 和 inout。input 由模块外部驱动。output 由模块内部驱动。inout 是双向的,适用于三态总线:模块既驱动又读取同一个引脚。内部端口绝大多数都是 input 或 output;inout 只出现在芯片引脚上。

output wire 和 output reg 有什么区别?

output wire 表示这个输出由连续赋值或子模块的输出驱动,也就是 always 块之外的一切。output reg 表示它由过程块(initial 或 always)驱动。这个关键字决定的是你能否在 always 内部给这个信号赋值,而不是综合出的硬件里有没有触发器。

Verilog 端口的 ANSI 风格是什么?

ANSI 风格直接在端口列表中声明每个端口的方向和类型:module foo(input wire [7:0] data, output reg [7:0] result);。较老的 Verilog-1995 风格只在端口列表里写名字,再在模块内部重新声明一遍。新代码请始终使用 ANSI 风格:更短,更不容易出错,也是所有现代代码的标准写法。

一个 Verilog 模块可以有多个输入和输出吗?

可以。一个模块需要多少端口就可以有多少,方向可以任意组合。在端口列表中用逗号分隔它们。端口列表的顺序决定了实例化时按位置连接的顺序,但你几乎总是使用按名称连接(.port(signal)),以免依赖顺序。

Coddy 编程语言插图

和 Coddy 一起学编程

开始