2. Verilog HDL基础(一):模块结构、数据类型(wire/reg)、连续赋值(assign)、运算符

各位同学,今天我们来聊聊Verilog HDL最基础的东西。说实话,很多新手一上来就急着写代码,结果连模块长什么样都没搞清楚。我当年带过一个实习生,写了三天代码,最后发现连端口方向都写反了……嗯,咱们先把地基打牢。

2.1 模块结构——Verilog的“黑盒子”

Verilog里最基本的单元就是模块(module)。你可以把它想象成一个黑盒子:外面能看到的是端口(输入输出),里面装的是具体逻辑。

一个完整的模块长这样:

module counter (
    input        clk,      // 时钟
    input        rst_n,    // 复位(低有效)
    input        en,       // 使能
    output reg [3:0] cnt   // 4位计数器输出
);

    // 内部逻辑
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            cnt <= 4'd0;
        else if (en)
            cnt <= cnt + 1'b1;
    end

endmodule

注意几个关键点:

  • 端口声明:input/output/inout 要写清楚。我见过有人把input写成output,仿真半天查不出错。
  • 模块名:最好有意义,别用module1、module2这种。
  • endmodule:别忘了这个结尾。少写一个字母,编译就报错。
我的习惯:端口声明时,我会把时钟和复位放在最前面,然后是控制信号,最后是数据信号。这样别人看代码一目了然。

2.2 数据类型——wire和reg

Verilog里最常用的两种数据类型就是wire和reg。很多初学者搞不清它们的区别,其实很简单:

类型 含义 赋值方式 典型用途
wire 线网型 连续赋值(assign) 组合逻辑、模块间连线
reg 寄存器型 过程赋值(always块内) 时序逻辑、存储状态

wire说白了就是一根导线。你给它赋值,它就保持那个值。比如:

wire a, b, c;
assign c = a & b;   // c的值由a和b实时决定

reg则像一个存储单元。它会在时钟沿到来时更新值。比如:

reg [7:0] data_reg;
always @(posedge clk) begin
    data_reg <= data_in;   // 时钟上升沿才更新
end
注意:reg并不一定综合成寄存器!如果你在always块里写组合逻辑(比如always @(*)),reg综合出来还是组合电路。我曾经被这个坑过——以为reg就是寄存器,结果综合出来一堆LUT。

2.3 连续赋值——assign语句

assign是Verilog里最直接的赋值方式。它的特点是:右边一变,左边立刻跟着变。就像电线一样,没有延迟。

基本语法:

assign 左边信号 = 右边表达式;

举个例子:

wire [3:0] sum;
wire [3:0] a, b;

assign sum = a + b;   // 加法器,a和b变化时sum立即变化

assign只能用于wire类型,不能用于reg。这个规则很死,别想着打破它。

实战经验:我在做数据通路设计时,经常用assign做简单的位拼接和选择。比如:
wire [7:0] data_out;
assign data_out = {data_high, data_low};  // 拼接
这样写比用always块简洁多了。

2.4 运算符——Verilog的“计算器”

Verilog的运算符和C语言很像,但有些细节不一样。我按类别给大家梳理一下:

2.4.1 算术运算符

  • + 加法
  • - 减法
  • * 乘法(综合会消耗大量资源)
  • / 除法(尽量别用,综合出来面积巨大)
  • % 取模(同样慎用)

注意:加减法没问题,乘除法要小心。我做过一个项目,用了除法器,结果面积翻了三倍……后来改成移位实现了。

2.4.2 逻辑运算符

  • && 逻辑与
  • || 逻辑或
  • ! 逻辑非

逻辑运算符返回的是1位布尔值(0或1)。

2.4.3 位运算符

  • & 按位与
  • | 按位或
  • ^ 按位异或
  • ~ 按位取反

位运算符是对每一位独立操作。比如 4'b1010 & 4'b1100 = 4'b1000。

2.4.4 关系运算符

  • > 大于
  • < 小于
  • >= 大于等于
  • <= 小于等于
  • == 等于
  • != 不等于

关系运算符返回1位布尔值。注意:===和!==是case相等比较,会考虑x和z状态,仿真时有用,但综合不支持。

2.4.5 移位运算符

  • << 左移
  • >> 右移

移位运算符在设计中非常常用。比如乘以2可以用左移1位实现:a << 1。

2.4.6 拼接运算符

{} 可以把多个信号拼在一起:

wire [7:0] byte_data;
wire [3:0] high, low;
assign byte_data = {high, low};  // 高4位和低4位拼接

2.5 知识体系总览

下面这张图把本章的核心知识点串起来了,你可以对照着看:

Verilog HDL基础 模块结构 module/endmodule 端口声明(input/output) 数据类型 wire(线网型) reg(寄存器型) 连续赋值 assign语句 组合逻辑描述 运算符 算术运算符 逻辑运算符 位运算符 关系运算符 移位/拼接运算符 核心:模块是骨架,wire/reg是血肉,assign是连接,运算符是逻辑
避坑指南:我曾经在项目中把wire和reg搞混,结果仿真通过了,综合却报错。后来养成一个习惯:先想清楚信号是组合逻辑还是时序逻辑,再决定用wire还是reg。这个习惯帮我省了不少调试时间。

好了,今天的内容就到这里。记住:模块结构是骨架,wire和reg是血肉,assign是连接线,运算符是逻辑核心。把这几个概念吃透,后面写代码就会顺手很多。

可可精品资料库