第二讲:Verilog基础语法(上)——模块与端口、数据类型、赋值语句、操作符

各位同学,今天我们正式开始啃Verilog的语法基础。说实话,语法这东西,光看书容易犯困。但你要是真写过几行代码,就会发现——嗯,其实就那么回事。

这一讲,我们聚焦四个核心:模块与端口、数据类型、赋值语句、操作符。把这四个搞明白,你就能看懂大部分Verilog代码了。

一、模块与端口:Verilog的“黑盒子”

Verilog里,所有设计都从模块(module)开始。模块就像芯片里的一个功能块,有输入、有输出,内部藏着逻辑。

我个人习惯把模块想象成一个黑盒子。你只管告诉它“输入什么”,它按规则算出“输出什么”。至于里面怎么算的,那是我们写代码的人决定的。

模块的基本结构:

module 模块名 (
    端口列表
);
    // 内部逻辑
endmodule

端口分三种:input(输入)、output(输出)、inout(双向)。我建议初学者先别碰inout,容易把自己绕晕。

举个例子,一个最简单的与门模块:

module and_gate (
    input  wire a,
    input  wire b,
    output wire y
);
    assign y = a & b;
endmodule

你看,端口声明里我写了wire。这是什么?我们接着聊。

二、数据类型:wire 和 reg

Verilog里最常用的两种数据类型就是wirereg。很多新手搞不清它们的区别,我当年也踩过坑。

类型 含义 典型用法
wire 连线型,组合逻辑 assign 赋值、模块端口连接
reg 寄存器型,时序逻辑 always 块内赋值

wire 说白了就是一根物理导线。它不存储值,只是把驱动端的值传递出去。你想想看,导线本身能存东西吗?不能。

reg 就不同了。它代表一个存储单元,可以保持上一次赋的值。注意,reg不一定真的综合成寄存器——如果你在组合逻辑的always块里用reg,它综合出来还是连线。嗯,这里要注意。

我的经验:端口声明时,input/output默认是wire类型。如果你需要输出是reg,必须显式写output reg。我曾经因为漏写这个,仿真对了,综合却报错,查了半天。

三、赋值语句:assign 和 always

赋值是Verilog的灵魂。两种方式:连续赋值(assign)过程赋值(always)

3.1 assign:组合逻辑的直通车

assign 用于组合逻辑。只要右边表达式变了,左边立刻更新。它只能对wire类型赋值。

assign y = a & b;  // 组合逻辑,y随a、b变化

我刚开始做设计时,喜欢把所有组合逻辑都用assign搞定。简单、直观、不容易出错。

3.2 always:时序逻辑的主战场

always 块可以描述组合逻辑,也可以描述时序逻辑。区别在于敏感列表。

// 组合逻辑:电平敏感
always @(*) begin
    y = a & b;
end

// 时序逻辑:边沿敏感
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        q <= 1'b0;
    else
        q <= d;
end

注意看,时序逻辑里我用了<=(非阻塞赋值),组合逻辑用了=(阻塞赋值)。这是硬性规定,搞反了仿真结果会一塌糊涂。

避坑指南:我曾经在一个项目里,把时序逻辑的赋值写成了阻塞赋值。仿真时看着没问题,综合后时序全乱套。从那以后,我给自己定了个规矩:always @(posedge clk) 里只用 <=。

四、操作符:Verilog的“计算器”

操作符没什么神秘的,就是C语言那套,加加减减、与或非。但有几个细节值得注意。

类别 操作符 说明
算术 + - * / % 乘除法综合出来面积大,慎用
逻辑 && || ! 返回1位布尔值
位运算 & | ~ ^ 按位操作,常用
归约 & | ^ ~& ~| ~^ 将多位数归约为1位
移位 << >> 逻辑移位,空位补0
拼接 { } 把多个信号拼在一起
条件 ? : 类似C的三目运算符

我个人最常用的是位运算拼接。比如你要把两个4位数拼成8位数:

wire [3:0] a, b;
wire [7:0] c = {a, b};  // 拼接

归约操作符也挺有意思。比如你想检查一个8位数是否全为0:

wire [7:0] data;
wire all_zero = ~|data;  // 归约或,再取反

你想想看,一行代码搞定,多清爽。

我的建议:操作符本身不难,难的是位宽匹配。两个不同位宽的数做运算,Verilog会自动扩展。扩展规则是:无符号数补0,有符号数补符号位。我见过太多因为位宽不匹配导致的bug了。

五、知识体系总览

为了帮你理清思路,我画了一张图。这张图把本章的核心知识点串在了一起。

Verilog基础语法(上)知识体系 模块与端口 module/endmodule input/output/inout 端口声明与连接 实例化 数据类型 wire(连线型) reg(寄存器型) 位宽声明 [N:0] 有符号/无符号 赋值语句 assign(连续赋值) always(过程赋值) 阻塞赋值 = 非阻塞赋值 <= 操作符 算术 位运算 归约 拼接 核心要点总结 1. 模块是Verilog的基本单元,端口是模块与外界通信的接口 2. wire用于组合逻辑,reg用于时序逻辑(但不仅限于时序) 3. assign实现连续赋值,always实现过程赋值 4. 时序逻辑用非阻塞赋值(<=),组合逻辑用阻塞赋值(=) 5. 操作符注意位宽匹配,避免综合出意外结果 —— 掌握这些,你已经能看懂80%的Verilog代码了 ——

这张图把模块、端口、数据类型、赋值语句、操作符串在了一起。你顺着箭头看,就能理解它们之间的关系。

好了,这一讲的内容就到这里。语法是工具,多用几次就熟了。下一讲我们继续深入,聊聊更进阶的语法结构。


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