2. Verilog基础(一):模块与端口、数据类型(wire/reg)、连续赋值(assign)、基础门级建模

好,咱们正式开始写代码了。

Verilog 这玩意儿,说白了就是描述数字电路的硬件语言。你写的每一行代码,最终都会变成芯片上实实在在的晶体管和连线。我刚开始学的时候,总把它当软件写,结果综合出来的电路乱七八糟。后来才明白——你得用电路的思维去写它。

2.1 模块与端口——芯片的“黑盒子”

模块(module)是 Verilog 最基本的构建单元。你可以把它想象成一个黑盒子,有输入、有输出,内部实现具体的功能。

module adder (
    input  [3:0] a,      // 4位输入
    input  [3:0] b,      // 4位输入
    output [4:0] sum     // 5位输出(考虑进位)
);
    assign sum = a + b;
endmodule

端口方向有三种:input、output、inout。我个人习惯把输入写在前面,输出写在后面,这样结构清晰。

命名规范建议:端口名用有意义的英文单词,比如 clk、rst_n、data_in。别用 a1、b2 这种,三个月后你自己都看不懂。

2.2 数据类型——wire 和 reg 的区别

这是新手最容易搞混的地方。我面试的时候,十个人里有八个说不清楚。

类型 本质 赋值方式 默认值 典型用途
wire 物理连线 连续赋值(assign)或模块端口连接 高阻态(z) 组合逻辑输出、模块间连线
reg 存储单元 过程赋值(always 块内) 不定态(x) 时序逻辑、寄存器、状态机

记住一个口诀:wire 是线,reg 是寄存器。但注意——reg 不一定会综合成寄存器。如果你在 always 块里写组合逻辑,reg 综合出来还是连线。

我曾经踩过的坑:在 always 块里给 wire 赋值,编译直接报错。反过来,用 assign 给 reg 赋值也不行。类型用错了,工具会毫不留情地给你一堆 error。

2.3 连续赋值(assign)——组合逻辑的“直通车”

assign 是组合逻辑的核心。它的意思是:右边信号一变,左边立刻更新。

wire [7:0] a, b;
wire [8:0] sum;
wire       carry;

assign {carry, sum} = a + b;  // 拼接操作,carry是进位

assign 的左边必须是 wire 类型。右边可以是任何表达式。你想想看,这就像用导线直接把两个加法器的输出连到总线上——没有延迟,没有时钟,纯组合逻辑。

实用技巧:用 assign 写简单的逻辑判断,比用 always 块更简洁。比如:
assign data_valid = (data_count > 0) && !fifo_empty;

2.4 基础门级建模——从晶体管到逻辑门

Verilog 支持直接调用基本门单元:and、or、nand、nor、xor、xnor、not、buf。

module basic_gates (
    input  a, b,
    output y1, y2, y3
);
    and  (y1, a, b);   // y1 = a & b
    or   (y2, a, b);   // y2 = a | b
    xor  (y3, a, b);   // y3 = a ^ b
endmodule

门级建模的语法是:门类型 (输出, 输入1, 输入2, ...)。第一个端口永远是输出,后面全是输入。

说实话,现在做数字芯片设计,很少有人手写门级网表了。综合工具会帮你把 RTL 代码映射到标准单元库。但理解门级建模有两个好处:

  • 写 testbench 时,偶尔需要模拟简单的门延迟
  • 做 FPGA 原型验证时,某些特殊接口需要手动例化 I/O 缓冲器
我的经验:门级建模在 ASIC 设计里主要用于后仿真的网表调试。有一次芯片回来功能不对,我就是在门级网表里追信号,发现一个 buffer 被优化掉了——这就是门级建模的价值。

2.5 本章知识体系

下面这张图把本章的核心概念串起来了:

Verilog基础(一)知识体系 模块与端口 数据类型 连续赋值 基础门级建模 input / output / inout wire(连线) reg(寄存器) assign 连续赋值 and / or / xor / not 组合逻辑 vs 时序逻辑 核心要点 • wire 用于 assign 和端口连接 • reg 用于 always 块内赋值 • 门级建模理解底层实现

2.6 避坑指南

最后,分享几个我当年踩过的坑:

  • wire 默认值是 z,不是 0。 如果你忘了给 wire 赋值,仿真里看到的就是高阻态。我曾经因为这个查了整整一天。
  • reg 默认值是 x。 在仿真里,未初始化的 reg 会传播不定态。所以复位逻辑一定要写清楚。
  • assign 不能写在 always 块里。 这是语法错误,工具会直接报错。
  • 门级建模的端口顺序不能错。 第一个必须是输出,后面是输入。顺序错了,综合出来的电路跟你想要的天差地别。
一句话总结:模块是骨架,端口是接口,wire 和 reg 是血肉,assign 是组合逻辑的直通车,门级建模是底层视角。把这几个概念吃透,Verilog 就算入门了。