3. Verilog基础与组合逻辑:模块化设计、assign语句、always块、wire与reg区别、常用运算符

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

这一章,我带你搞定Verilog里最基础、也最核心的几个概念。说白了,就是组合逻辑的几种写法。你想想看,高频交易里那些加法器、比较器、数据选择器,本质上都是组合逻辑。搞懂了这些,你就能看懂大部分FPGA代码了。

3.1 模块化设计——把大问题拆小

FPGA设计不是写一个超级大的代码块。我习惯把整个系统拆成一个个小模块。每个模块干一件事,然后像搭积木一样拼起来。

举个例子,一个简单的加法器模块:

module adder (
    input  [7:0] a,      // 输入a,8位宽
    input  [7:0] b,      // 输入b,8位宽
    output [8:0] sum     // 输出和,9位宽(防止溢出)
);

    assign sum = a + b;

endmodule

你看,moduleendmodule就是模块的边界。端口列表里声明了输入输出。内部实现就是一行assign

我的习惯:每个模块只做一件事。比如加法器只做加法,比较器只做比较。这样调试起来特别方便。我在项目中见过有人把加减乘除全写在一个模块里,结果出bug时根本不知道是哪部分的问题。

3.2 assign语句——连续赋值

assign是组合逻辑最常用的写法。它的意思是:只要右边信号变了,左边立刻更新。没有延迟,没有时钟。

assign y = a & b;   // y = a 与 b
assign z = a ^ b;   // z = a 异或 b

这里要注意:assign左边必须是wire类型,不能是reg。为什么?因为assign是连续驱动,wire天生就是干这个的。

避坑指南:我曾经在项目里把assign左边写成了reg,结果综合报错。后来养成了习惯:看到assign,左边一定是wire

3.3 always块——更灵活的组合逻辑

always块也能描述组合逻辑。但写法跟assign不太一样。

always @(*) begin
    if (sel)
        y = a;
    else
        y = b;
end

这里的@(*)表示:只要块里用到的信号变了,就重新执行。对于组合逻辑,always块里必须用阻塞赋值=),不能用非阻塞(<=)。

嗯,这里要注意:always块里的变量必须是reg类型。但别被名字骗了——组合逻辑里的reg并不生成寄存器,它只是语法要求。

写法 左边类型 适用场景
assign wire 简单组合逻辑(加法、与或非)
always @(*) reg 复杂组合逻辑(if-else、case)

3.4 wire与reg的区别——别再搞混了

很多初学者在这栽跟头。我直接说结论:

  • wire:连线。只能被assign或模块端口驱动。不能存储值。
  • reg:变量。可以在always块里赋值。但组合逻辑里它不存值,只是语法需要。

举个例子:

wire w1;
reg  r1;

assign w1 = a & b;   // 正确

always @(*) begin
    r1 = a & b;      // 正确
end

// assign r1 = a & b;  // 错误!assign左边不能是reg
// always @(*) begin
//     w1 = a & b;    // 错误!always块里不能给wire赋值
// end
记住这个口诀:assign配wire,always配reg。组合逻辑里,reg不存值,它只是个名字。

3.5 常用运算符——够用就行

Verilog的运算符很多,但高频交易里常用的就这些:

类别 运算符 说明
算术 + - * / 加减乘除。注意乘法会消耗大量LUT
位运算 & | ^ ~ 与、或、异或、非。组合逻辑主力
逻辑 && || ! 逻辑与、或、非。用于条件判断
比较 > < == != 大于、小于、等于、不等
移位 << >> 左移、右移。乘2除2的快速实现
拼接 {a, b} 把多个信号拼成一个

举个例子,一个简单的数据选择器:

module mux2to1 (
    input       sel,
    input [7:0] d0, d1,
    output [7:0] y
);

    assign y = sel ? d1 : d0;

endmodule

你看,一行assign加一个三目运算符,搞定。

我的经验:高频交易里最常用的是加法、比较和位运算。乘法尽量少用,除非你用DSP硬核。我曾经优化过一个行情解析模块,把乘法换成移位加加法,延迟直接降了30%。

3.6 本章知识体系

下面这张图,帮你理清本章的核心逻辑:

组合逻辑实现方式 模块化设计 assign语句 always块 wire类型 左边必须是wire 左边必须是reg 常用运算符 算术 | 位运算 | 逻辑 | 比较 | 移位 | 拼接 组合逻辑 = 模块化 + 赋值方式 + 数据类型 + 运算符

这张图把本章的知识点串起来了。你想想看,从模块化设计开始,到两种赋值方式(assign和always),再到对应的数据类型(wire和reg),最后用运算符实现具体功能。这就是组合逻辑的完整链路。

总结一下:
  • 模块化:每个模块干一件事
  • assign:连续赋值,左边wire
  • always:过程赋值,左边reg
  • wire是连线,reg是变量(组合逻辑里不存值)
  • 运算符:够用就行,别炫技

好了,这一章就到这。下一章我们聊时序逻辑——就是带时钟的那种。到时候你会看到always @(posedge clk)这种写法,跟组合逻辑的always @(*)完全不一样。


专注资料整理