模块与端口:从零开始搭积木

各位同学,今天我们来聊聊RTLA里最基础、也最核心的东西——模块与端口。

你想想看,数字芯片设计说白了就是搭积木。每个模块就是一块积木,端口就是积木上的插头和插座。模块之间通过端口互相连接,最终拼成一个完整的系统。

我个人习惯把模块比作一个黑盒子。你不需要知道盒子里面怎么实现的,只要知道它有哪些输入、哪些输出,就能把它用起来。这就是模块化的思想。

module声明:给你的积木起个名字

在RTLA里,声明一个模块很简单。用module关键字,后面跟上模块名,然后是一对括号,里面放端口列表。

module counter (
    input  wire       clk,
    input  wire       rst_n,
    output reg  [7:0] count
);
    // 模块内部逻辑
endmodule

嗯,这里要注意:模块名最好用有意义的英文单词,别用a、b、c这种。我在项目中见过有人用module m1,三个月后他自己都看不懂了。

端口方向:数据该往哪走

端口方向有三种:inputoutputinout

方向 含义 典型用途
input 数据流入模块 时钟、复位、数据输入
output 数据流出模块 计算结果、状态信号
inout 双向数据 I2C数据线、三态总线

inout用得比较少,但一旦用上就得小心。我曾经在一个I2C控制器里把inout的方向搞反了,仿真怎么都通不过。后来发现是驱动使能信号写反了,折腾了一整天。

避坑指南:inout端口必须配合三态门使用。千万别把inout当普通输入输出用,否则综合时会报错。

端口类型:wire、reg、logic

端口类型决定了这个信号怎么被赋值。

  • wire:连线型。由组合逻辑驱动,或者直接连到其他模块的输出。
  • reg:寄存器型。在always块里赋值,会综合成触发器或锁存器。
  • logic:RTLA里新增的类型。它既可以当wire用,也可以当reg用。

我个人强烈推荐用logic。为什么?因为它省事。你不用纠结这个信号到底该声明成wire还是reg,统统用logic就对了。

module adder (
    input  logic [3:0] a,
    input  logic [3:0] b,
    output logic [4:0] sum
);
    always_comb begin
        sum = a + b;
    end
endmodule

你看,这里sum在always_comb里赋值,用logic完全没问题。如果换成Verilog,你得声明成reg,但实际综合出来是组合逻辑,容易让人困惑。

小技巧:在RTLA里,我建议你统一用logic。除非你要用多驱动(比如三态总线),那种情况才需要wire。

参数化模块:让模块更灵活

参数化是模块设计的精髓。通过parameter,你可以让一个模块适应不同的位宽、不同的深度。

module fifo #(
    parameter DATA_WIDTH = 8,
    parameter DEPTH      = 16
) (
    input  logic                clk,
    input  logic                rst_n,
    input  logic                wr_en,
    input  logic [DATA_WIDTH-1:0] wr_data,
    input  logic                rd_en,
    output logic [DATA_WIDTH-1:0] rd_data,
    output logic                full,
    output logic                empty
);
    // FIFO实现
endmodule

实例化的时候,你可以覆盖这些参数:

fifo #(
    .DATA_WIDTH(16),
    .DEPTH(32)
) u_fifo (
    .clk(clk),
    .rst_n(rst_n),
    // ...
);

我记得有一次做项目,需要同时用8位和16位的FIFO。如果写两个模块,代码量翻倍不说,维护也麻烦。用参数化模块,改一下参数就行,省心多了。

核心要点:参数化模块让你的代码可复用、可配置。设计时多花5分钟加参数,后续能省5小时。

知识体系总览

下面这张图帮你理清模块与端口的关系:

模块 module counter input wire clk input wire rst_n output reg [7:0] parameter WIDTH=8 输入端口 输出端口 参数化配置 模块与端口结构图 数据从输入端口流入,经模块处理,从输出端口流出

这张图很直观:左边是输入端口,右边是输出端口,中间是模块内部逻辑。参数化配置让模块可以灵活调整。

避坑总结

最后,我把自己踩过的坑总结一下:

  • 端口方向别搞反:input和output写反了,仿真能过,但综合会报错。我吃过这个亏。
  • 参数默认值要合理:给parameter设个合理的默认值,别人用你的模块时不用每次都改。
  • 端口名别太短clkrst没问题,但dq这种就太抽象了。多写几个字母,代码可读性翻倍。
  • inout慎用:除非你真的很清楚三态门怎么用,否则别碰inout。

好了,模块与端口就讲到这里。记住一句话:模块是骨架,端口是血脉。骨架搭好了,血脉通顺了,芯片设计就成功了一半。


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