03. wire、reg、parameter、端口与模块
选择 wire 还是 reg,最可靠的问题不是“它是不是一根线”,而是:这个信号由什么语句驱动?

wire:线网
跳转到“wire:线网”wire 的值由驱动源持续决定。常见驱动源包括连续赋值、模块输出和门实例:
wire and_result;assign and_result = a & b;
wire child_done;worker u_worker ( .clk (clk), .done (child_done));没有驱动的线网在四态仿真中通常为 z。一般不要让多个普通逻辑同时驱动一条 wire;若驱动值冲突,仿真可能得到 x,硬件也可能无法实现。
reg:过程赋值变量
跳转到“reg:过程赋值变量”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;ANSI 风格端口声明
跳转到“ANSI 风格端口声明”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));点号后是 子模块端口名,括号内是 当前层信号名。命名连接比按位置连接更安全:增加端口或调整声明顺序时,不容易悄悄接错。
检查实例化要逐项确认:
- 方向是否合理;
- 位宽是否一致;
- 时钟和复位极性是否一致;
- 一个信号是否被多个输出驱动;
- 参数是否处于允许范围。
模块边界怎么划分
跳转到“模块边界怎么划分”好的模块通常具备:
- 单一、清晰的职责;
- 接口比内部实现简单;
- 时钟域边界明确;
- 参数少而有意义;
- 能被独立 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。