跳转到内容
新建笔记

03. wire、reg、parameter、端口与模块

选择 wire 还是 reg,最可靠的问题不是“它是不是一根线”,而是:这个信号由什么语句驱动?

wire 与 reg 的驱动关系

wire 的值由驱动源持续决定。常见驱动源包括连续赋值、模块输出和门实例:

wire and_result;
assign and_result = a & b;
wire child_done;
worker u_worker (
.clk (clk),
.done (child_done)
);

没有驱动的线网在四态仿真中通常为 z。一般不要让多个普通逻辑同时驱动一条 wire;若驱动值冲突,仿真可能得到 x,硬件也可能无法实现。

reg 可以在 always 或 initial 过程块中赋值:

reg y;
always @(*) begin
y = a & b;
end

尽管名字叫 reg,上例会综合为组合与门,不会生成寄存器。是否生成寄存器取决于过程的触发方式和是否需要保存旧值:

reg q;
always @(posedge clk) begin
q <= d;
end

这个例子才会综合为触发器。

记忆:wire/reg 是 Verilog 的连接与赋值规则;组合/时序是最终硬件性质。两组概念有关,但不是同义词。

参数 parameter 与 localparam

跳转到“参数 parameter 与 localparam”

参数让同一个模块适配不同位宽或计数范围:

module adder #(
parameter WIDTH = 8
) (
input wire [WIDTH-1:0] a,
input wire [WIDTH-1:0] b,
output wire [WIDTH:0] sum
);
assign sum = {1'b0, a} + {1'b0, b};
endmodule

实例化时覆盖参数:

wire [16:0] sum16;
adder #(
.WIDTH(16)
) u_adder16 (
.a (a16),
.b (b16),
.sum (sum16)
);

parameter 是模块对外可配置项;localparam 是模块内部不允许上层覆盖的常量,适合固定状态编码或派生值:

localparam STATE_IDLE = 2'b00;
localparam STATE_RUN = 2'b01;

Verilog-2001 推荐直接在端口列表里声明方向、类型和位宽:

module comparator #(
parameter WIDTH = 8
) (
input wire [WIDTH-1:0] a,
input wire [WIDTH-1:0] b,
output wire a_greater,
output reg [WIDTH-1:0] max_value
);
assign a_greater = (a > b);
always @(*) begin
if (a > b)
max_value = a;
else
max_value = b;
end
endmodule

为什么 a_greater 是 wire,而 max_value 是 reg?因为前者由 assign 驱动,后者在 always 中赋值。

输入、输出和双向端口

跳转到“输入、输出和双向端口”
  • input:模块内部读取,通常视作线网;
  • output wire:由连续赋值或子模块输出驱动;
  • output reg:在本模块过程块中赋值;
  • inout wire:双向三态端口,主要用于芯片外部总线。

除非确实连接双向物理引脚,不要为了“省端口”随意使用 inout。

实例化:推荐命名连接

跳转到“实例化:推荐命名连接”
comparator #(
.WIDTH(8)
) u_comparator (
.a (data_a),
.b (data_b),
.a_greater (is_greater),
.max_value (max_data)
);

点号后是 子模块端口名,括号内是 当前层信号名。命名连接比按位置连接更安全:增加端口或调整声明顺序时,不容易悄悄接错。

检查实例化要逐项确认:

  1. 方向是否合理;
  2. 位宽是否一致;
  3. 时钟和复位极性是否一致;
  4. 一个信号是否被多个输出驱动;
  5. 参数是否处于允许范围。

好的模块通常具备:

  • 单一、清晰的职责;
  • 接口比内部实现简单;
  • 时钟域边界明确;
  • 参数少而有意义;
  • 能被独立 testbench 验证。

顶层模块适合负责引脚、时钟复位和子模块连接;大量业务逻辑塞进顶层,会让复用和验证困难。

注释、关键字与大小写

跳转到“注释、关键字与大小写”
// 单行注释
/*
* 多行注释
*/

Verilog 区分大小写;module、always、wire 等关键字不能作为普通标识符。注释应解释目的、单位、边界条件和原因,不要重复代码字面含义。

wire result;
always @(*) result = a & b; // 错:过程赋值目标不能是普通 wire
reg result2;
assign result2 = a & b; // 错:连续赋值目标应为 net/wire

另一个高频错误是把 reg 当作“自动保存”:组合过程若所有路径都赋值,reg 不会保存旧值;若路径不完整,反而可能意外产生锁存器。

写一个参数化二选一模块:数据宽度由 WIDTH 决定,sel=0 输出 a,否则输出 b。分别用 assign 和 always @(*) 写两版,并说明输出为何分别声明为 wire 和 reg。

上一篇:四态逻辑与常量 · 返回目录 · 下一篇:运算符与位宽推导