第二讲:组合逻辑基础——assign语句、连续赋值与基本门电路

各位同学,欢迎来到第二讲。

上一讲我们搭建好了开发环境,也跑了第一个点亮LED的程序。今天我们要深入一点,聊聊组合逻辑的基石。说白了,就是怎么用Verilog描述那些“输入一变,输出立马跟着变”的电路。

我刚开始学Verilog的时候,总觉得assign语句太简单了,不就是个等号嘛。后来做项目才发现,这里面的门道还真不少。咱们今天就把这块地基打扎实。

一、连续赋值与assign语句

先看一个最简单的例子:

module and_gate(
    input  wire a,
    input  wire b,
    output wire y
);

assign y = a & b;

endmodule

这个assign y = a & b; 就是连续赋值。它的意思是:只要a或b任何一个发生变化,y就会立刻重新计算。注意这个“立刻”——在硬件里,它对应的是实实在在的连线,不是软件里的变量赋值。

关键点:assign语句的左边必须是wire类型,不能是reg。右边可以是wire或reg,但最终综合出来都是组合逻辑。

我个人习惯把assign看作是“硬件连线”的声明。你写assign y = a & b; 就等于告诉综合工具:“给我拉一根线,把a和b经过与门后的结果连到y上。”

二、基本门电路的Verilog实现

咱们把五种基本门电路一次性讲完。先看代码:

module basic_gates(
    input  wire a,
    input  wire b,
    output wire y_and,
    output wire y_or,
    output wire y_not,
    output wire y_nand,
    output wire y_nor,
    output wire y_xor,
    output wire y_xnor
);

// 与门
assign y_and = a & b;

// 或门
assign y_or  = a | b;

// 非门
assign y_not = ~a;

// 与非门
assign y_nand = ~(a & b);

// 或非门
assign y_nor  = ~(a | b);

// 异或门
assign y_xor  = a ^ b;

// 同或门
assign y_xnor = a ~^ b;

endmodule

这里我用的是Verilog的运算符:&、|、~、^、~^。它们直接对应硬件里的门电路。你想想看,写代码的时候脑子里就要有电路图——a和b经过一个与门,输出接到y_and上。

小技巧:异或门在项目中特别常用。比如做奇偶校验、数据比较、甚至简单的加密。我在一个通信项目里就用异或门实现了CRC校验的并行计算,比串行快了8倍。

三、多输入与多位的组合逻辑

实际项目中很少只处理1位信号。咱们看看多位的写法:

module wide_gates(
    input  wire [7:0] data_a,
    input  wire [7:0] data_b,
    output wire [7:0] result_and,
    output wire [7:0] result_or,
    output wire [7:0] result_xor
);

// 8位逐位与
assign result_and = data_a & data_b;

// 8位逐位或
assign result_or  = data_a | data_b;

// 8位逐位异或
assign result_xor = data_a ^ data_b;

endmodule

这里data_a和data_b都是8位宽。assign语句会逐位进行运算——data_a[0]和data_b[0]做与,结果给result_and[0],以此类推。综合工具会自动生成8个并行的门电路。

曾经踩过的坑:有一次我把8位数据的异或写成了 data_a ^ data_b,结果发现综合出来的面积比预期大了一倍。后来一查,原来我忘了给结果指定位宽,工具默认生成了32位的运算器。所以写代码时一定要明确指定位宽。

四、组合逻辑的优先级与括号

组合逻辑里运算符有优先级,但我建议你——别依赖优先级,多用括号。看个例子:

// 不推荐:依赖优先级
assign y = a & b | c & d;

// 推荐:明确括号
assign y = (a & b) | (c & d);

这两种写法综合出来的电路是一样的。但第二种可读性更好,也更容易维护。我在团队里定过一个规矩:所有组合逻辑表达式必须加括号,除非是单运算符。这个规矩帮我们减少了不少调试时间。

五、知识体系结构图

下面这张图总结了本章的核心内容:

组合逻辑基础:知识体系 assign 连续赋值 基本门电路 单输入:非门 (~) 双输入:与(&)、或(|)、异或(^) 复合:与非、或非、同或 实战要点 assign左边必须是wire 多用括号,少依赖优先级 明确指定位宽 核心:硬件思维,代码即电路

六、常见问题与避坑指南

讲几个我实际项目中遇到的问题:

  • 问题1:assign语句里用了reg变量。记住,assign左边必须是wire。如果你非要用reg,那就得用always块。
  • 问题2:多位信号运算时位宽不匹配。比如8位和16位做与运算,结果会怎么样?工具会帮你扩展,但扩展的是高位补0还是符号扩展?这取决于你用的是wire还是signed。我建议:运算前手动对齐位宽。
  • 问题3:组合逻辑环路。比如 assign a = b; assign b = a; 这种写法会形成组合环路,综合时会报错或者生成不可预测的电路。我曾经在一个项目里不小心写出了这种代码,仿真没问题,但上板子就死机。查了两天才发现是组合环路。

我的经验:写组合逻辑时,脑子里要时刻想着“这是一根线”。如果这根线从输出又绕回了输入,那就形成了环路。好的习惯是:先画电路图,再写代码。哪怕只是草稿,也能帮你避免很多低级错误。

七、总结

这一讲我们学了:

  • assign语句的本质是硬件连线
  • 五种基本门电路的Verilog写法
  • 多位信号的并行运算
  • 括号的使用和优先级问题
  • 常见的组合逻辑陷阱

嗯,内容不算多,但都是基本功。下一讲我们会把这些门电路组合起来,实现一些实用的功能模块。记住:硬件设计没有捷径,基础打牢了,后面才能飞起来。


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