3、Verilog基础(上):模块与端口、数据类型(wire/reg)、组合逻辑(assign/always)、运算符

各位同学,今天咱们聊聊Verilog的基础。说实话,这部分内容看起来简单,但恰恰是很多新手翻车的重灾区。我见过不少项目,代码写得花里胡哨,结果综合出来一堆问题,追根溯源,都是基础没打牢。

3.1 模块与端口:芯片的“黑盒子”思维

Verilog里,模块(module)是基本单元。你可以把它想象成一个黑盒子——有输入、有输出,内部怎么实现,外面不用管。

我个人习惯,写模块前先画个框图。把输入输出引脚标清楚,再动手写代码。这样思路清晰,不容易漏信号。

module counter (
    input  wire       clk,      // 时钟
    input  wire       rst_n,    // 复位,低有效
    input  wire       en,       // 使能
    output reg  [3:0] count     // 计数值
);
    // 内部逻辑
endmodule

注意看端口声明:input、output、inout。其中inout是双向端口,新手慎用。我在项目中吃过亏——双向端口处理不好,容易产生驱动冲突,调试起来很头疼。

端口类型速查表

方向关键字说明
输入input信号从外部流入模块
输出output信号从模块流出到外部
双向inout既可输入也可输出,需配合三态门

3.2 数据类型:wire 与 reg 的“爱恨情仇”

很多初学者搞不清wire和reg的区别。说白了:

  • wire:连线,表示物理连接。它本身不存储值,值由驱动源决定。
  • reg:寄存器,表示存储单元。它在过程赋值(always块)中保持值。

但注意!reg不一定会综合成寄存器。如果你在always块里写组合逻辑,reg综合出来只是连线。嗯,这里要特别提醒:综合工具看的是你的写法,不是数据类型名字。

我的经验:在组合逻辑的always块里,我习惯用reg类型;在assign语句里,我用wire。这样代码可读性更好,别人一看就知道你的意图。

// wire 用法:assign 连续赋值
wire [7:0] sum;
assign sum = a + b;

// reg 用法:always 过程赋值
reg [7:0] sum_reg;
always @(*) begin
    sum_reg = a + b;
end

你想想看,上面两段代码功能完全一样,但写法不同。综合出来的电路也基本一致。所以别纠结名字,关键是理解背后的硬件行为。

3.3 组合逻辑:assign 与 always 的“双雄对决”

组合逻辑,就是输出只取决于当前输入,没有记忆功能。实现方式有两种:

3.3.1 assign 语句

assign是连续赋值,左边必须是wire类型。它描述的是“一直连接”的关系。

assign y = (sel) ? a : b;  // 二选一多路器

这种写法简洁明了,适合简单的组合逻辑。我曾经在项目中用assign写了一个复杂的加法树,综合出来面积和时序都很好。

3.3.2 always 语句

always块更灵活,可以写if-else、case等结构。但要注意:

  • 组合逻辑的敏感列表用@(*),表示所有输入信号变化都触发。
  • 块内赋值用阻塞赋值=,非阻塞赋值<=用于时序逻辑。
always @(*) begin
    if (sel)
        y = a;
    else
        y = b;
end

避坑指南:我曾经在组合逻辑的always块里用了非阻塞赋值<=,结果仿真和综合结果不一致。查了两天才发现——组合逻辑必须用阻塞赋值!记住:组合逻辑用=,时序逻辑用<=。

3.4 运算符:从算术到位操作

Verilog的运算符很丰富,我挑几个重点说说。

类别运算符示例说明
算术+ - * / %a + b加减乘除取模
位运算& | ~ ^a & b与、或、非、异或
逻辑&& || !a && b逻辑与、或、非
关系> < >= <= == !=a > b比较运算
移位<< >>a << 2左移、右移
拼接{ }{a, b}位拼接
条件? :sel ? a : b三目运算符

这里有个容易混淆的点:位运算 vs 逻辑运算。位运算是对每个bit操作,逻辑运算结果是布尔值(0或1)。

wire [3:0] a = 4'b1010;
wire [3:0] b = 4'b1100;

assign bit_and = a & b;   // 4'b1000
assign log_and = a && b;  // 1'b1(因为a和b都不为0)

我个人习惯,在条件判断里用逻辑运算,在数据通路里用位运算。这样代码意图清晰,不容易出错。

运算符优先级提醒:

优先级从高到低:! ~ > 算术 > 移位 > 关系 > 位运算(& | ^) > 逻辑(&& ||) > 条件(?:)

记不住?那就加括号!我写代码时,只要不确定优先级,一律加括号。这样既保险,又方便别人阅读。

3.5 知识体系总览

下面这张图,把本章的核心知识点串起来了。你可以把它当作学习地图。

Verilog基础(上)知识体系 模块与端口 数据类型 组合逻辑 运算符 input / output / inout wire / reg assign / always 端口声明、模块实例化 连线 vs 存储、综合行为 阻塞 vs 非阻塞、敏感列表 算术/位 逻辑/关系 移位/拼接 条件运算符

这张图展示了本章的四个核心模块:模块与端口是骨架,数据类型是血肉,组合逻辑是行为,运算符是工具。四者结合,就能写出基本的Verilog代码。

学习建议:别急着写复杂代码。先把模块声明、wire/reg区别、assign/always用法练熟。我当年就是基础没打牢,后面写状态机时各种踩坑。基础扎实了,后面学起来飞快。

好了,这一章的内容就到这里。记住:Verilog是硬件描述语言,不是软件编程语言。写代码时,脑子里要想着电路,而不是流程。下一章我们继续聊时序逻辑和状态机,到时候会有更多实战技巧分享。

可可精品资料库