第二讲:组合逻辑基础——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);
这两种写法综合出来的电路是一样的。但第二种可读性更好,也更容易维护。我在团队里定过一个规矩:所有组合逻辑表达式必须加括号,除非是单运算符。这个规矩帮我们减少了不少调试时间。
五、知识体系结构图
下面这张图总结了本章的核心内容:
六、常见问题与避坑指南
讲几个我实际项目中遇到的问题:
- 问题1:assign语句里用了reg变量。记住,assign左边必须是wire。如果你非要用reg,那就得用always块。
- 问题2:多位信号运算时位宽不匹配。比如8位和16位做与运算,结果会怎么样?工具会帮你扩展,但扩展的是高位补0还是符号扩展?这取决于你用的是wire还是signed。我建议:运算前手动对齐位宽。
- 问题3:组合逻辑环路。比如 assign a = b; assign b = a; 这种写法会形成组合环路,综合时会报错或者生成不可预测的电路。我曾经在一个项目里不小心写出了这种代码,仿真没问题,但上板子就死机。查了两天才发现是组合环路。
我的经验:写组合逻辑时,脑子里要时刻想着“这是一根线”。如果这根线从输出又绕回了输入,那就形成了环路。好的习惯是:先画电路图,再写代码。哪怕只是草稿,也能帮你避免很多低级错误。
七、总结
这一讲我们学了:
- assign语句的本质是硬件连线
- 五种基本门电路的Verilog写法
- 多位信号的并行运算
- 括号的使用和优先级问题
- 常见的组合逻辑陷阱
嗯,内容不算多,但都是基本功。下一讲我们会把这些门电路组合起来,实现一些实用的功能模块。记住:硬件设计没有捷径,基础打牢了,后面才能飞起来。
公众号:蓝海资料掘金营,微信deep3321