3. 模块与端口:module/endmodule结构、input/output/inout端口、端口位宽声明

好,咱们今天聊聊模块与端口。这是Verilog里最基础、也最核心的东西。说白了,你写Verilog就是在画一个又一个的“黑盒子”,而模块就是这个盒子,端口就是盒子上的引脚。

3.1 module/endmodule结构——你的第一个“黑盒子”

每个Verilog设计,都是从module开始,到endmodule结束。这个结构就像C语言里的main()函数,但更灵活。一个模块可以调用另一个模块,这就是层次化设计。

我个人习惯,写模块时先把端口声明写完,再写内部逻辑。这样思路清晰,不容易漏东西。

module counter (
    input  wire       clk,      // 时钟
    input  wire       rst_n,    // 复位,低有效
    output reg  [7:0] count     // 8位计数器输出
);

// 内部逻辑
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        count <= 8'd0;
    else
        count <= count + 1'b1;
end

endmodule

你看,这个结构很清晰。模块名是counter,端口列表写在括号里,最后用endmodule收尾。嗯,这里要注意:endmodule后面不要加分号,这是新手常犯的错。

3.2 input/output/inout端口——三种引脚类型

端口有三种类型,我分别说说。

3.2.1 input(输入端口)

输入端口,数据从外部流入模块内部。它必须是wire类型。为什么?因为输入端口是被外部驱动的,模块内部只能读它,不能写它。

module and_gate (
    input  wire a,   // 输入a
    input  wire b,   // 输入b
    output wire y    // 输出y
);

assign y = a & b;

endmodule

3.2.2 output(输出端口)

输出端口,数据从模块内部流向外部。它可以是wirereg类型。用assign赋值时用wire,用always块赋值时用reg

我在项目中遇到过一个问题:有个同事把输出端口声明为wire,却在always块里给它赋值,结果编译报错。说白了,wire只能被连续赋值,reg才能被过程赋值。

3.2.3 inout(双向端口)

双向端口,既能输入也能输出。这个用得不多,但在总线接口(比如I2C的SDA线)里很常见。使用时要特别小心,需要三态门控制。

module bidir (
    inout wire data,   // 双向数据线
    input  wire en,    // 输出使能
    input  wire din    // 要输出的数据
);

assign data = en ? din : 1'bz;  // en=1时输出,en=0时高阻

endmodule

你想想看,如果两个模块同时驱动一根线,会发生什么?短路!所以inout端口必须用三态门,不输出时就置为高阻z

3.3 端口位宽声明——别让你的数据“溢出”

端口位宽,说白了就是这根“线”能传多少位数据。声明方式很简单:[MSB:LSB]

声明方式 含义 示例
input [7:0] data 8位输入,data[7]是最高位 data[7:0] = 8'hFF
output [15:0] result 16位输出,result[15]是最高位 result[15:0] = 16'hABCD
inout [3:0] bus 4位双向总线 bus[3:0] = 4'bz

我曾经犯过一个错:声明了一个[0:7]的端口,结果后面代码里用了[7:0]的索引,仿真时数据全乱了。所以,我建议统一用[MSB:LSB](高位在左,低位在右)的写法,别混用。

核心要点:

  • 端口位宽必须与内部逻辑匹配,否则综合时会出警告
  • 未声明位宽的端口默认是1位
  • 位宽声明写在端口名前面,不是后面

3.4 知识体系结构图

下面这张图,帮你理清模块与端口的关系。我画了张SVG,你看一眼就明白了。

module 模块名 内部逻辑 always / assign / 实例化 endmodule input output inout input [7:0] data output [15:0] result inout [3:0] bus

小技巧:写端口声明时,我习惯把同一类型的端口放在一起,按位宽从大到小排列。这样代码看起来整齐,也方便后续维护。

警告:千万别把input端口声明成reg类型!综合工具会报错。记住:input只能是wireoutput可以是wirereginout只能是wire

好了,模块与端口这部分就讲到这里。记住:模块是设计的骨架,端口是骨架上的关节。骨架搭好了,后面的逻辑填充才能顺风顺水。


公众号:蓝海资料掘金营,微信deep3321