第一章:数字逻辑基础与Verilog初识
各位同学好,我是你们的FPGA讲师。今天咱们从最底层开始,聊聊数字逻辑到底是怎么回事。说实话,很多初学者一上来就写Verilog代码,结果连最基本的“0”和“1”在硬件里是怎么来的都搞不清楚。我个人觉得,先把地基打牢,后面写RISC-V内核时才不会慌。
1.1 从晶体管到逻辑门
数字电路的基础是什么?是晶体管。说白了,晶体管就是一个开关。给它一个电压,它就导通;不给,它就断开。我们用这个开关来实现“0”和“1”。
举个例子,一个NMOS管,栅极接高电平时,源漏之间导通,输出低电平;栅极接低电平时,管子关断,输出高电平(通过上拉电阻)。这就是最简单的反相器——非门。
我在项目中遇到过一个问题:某次做低功耗设计,发现漏电流太大。查了半天,原来是晶体管阈值电压选低了。嗯,这里要注意,工艺库里的晶体管参数直接影响你的功耗和速度。
核心概念:逻辑门就是晶体管的组合。与门、或门、非门,这三个是最基本的。任何复杂的数字电路,包括RISC-V处理器,最终都是这些门的组合。
你想想看,一个32位的加法器,里面有多少个晶体管?几万个。但设计时我们不会一个个去画晶体管,而是用硬件描述语言(HDL)来抽象描述。
1.2 组合逻辑与时序逻辑基础
数字电路分两大类:组合逻辑和时序逻辑。这个区分很重要,我当年刚入行时经常搞混。
组合逻辑:输出只取决于当前输入。比如一个加法器,输入A和B,输出就是A+B。没有记忆功能。
时序逻辑:输出不仅取决于当前输入,还取决于之前的状态。比如一个计数器,它需要记住当前计数值。这就靠触发器(Flip-Flop)来实现。
为什么会这样?因为时序逻辑里有“时钟”这个家伙。时钟的上升沿或下降沿到来时,触发器才采样输入,更新输出。其他时间,输出保持不变。
我的经验:写Verilog时,组合逻辑用assign或always@(*)块,时序逻辑用always@(posedge clk)块。这个区分一定要刻在脑子里。我曾经见过一个同事把时序逻辑写成了组合逻辑,结果仿真没问题,上板子就跑飞了。
下面这张图展示了数字逻辑的基本分类和关系:
1.3 Verilog HDL语言概述与历史
Verilog诞生于1984年,由Gateway Design Automation公司开发。后来被Cadence收购,再后来成为IEEE标准(IEEE 1364)。
同期还有VHDL,但Verilog更简洁,更像C语言,所以做IC设计和FPGA的人用得更多。我个人习惯用Verilog,因为写起来快,调试也方便。
Verilog的核心能力是什么?就是用文本描述硬件。你可以描述一个门,也可以描述一个完整的RISC-V处理器。它支持不同抽象层次:
- 行为级描述:用always块描述功能,不关心具体门电路
- RTL级描述:寄存器传输级,描述数据如何在寄存器间流动
- 门级描述:直接例化与门、或门等基本单元
注意:Verilog是硬件描述语言,不是编程语言。写Verilog时,脑子里要想的是电路,不是软件的执行流程。我曾经带过一个软件转硬件的工程师,他总喜欢用for循环写组合逻辑,结果综合出来的电路面积大得吓人。
1.4 第一个Verilog模块:半加器
好了,理论讲完了,咱们动手写第一个模块。半加器是最简单的加法电路,它把两个1位二进制数相加,输出和(Sum)与进位(Carry)。
真值表如下:
| A | B | Sum | Carry |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 0 | 1 |
从真值表可以看出:Sum = A XOR B,Carry = A AND B。就这么简单。
下面是用Verilog实现的半加器:
module half_adder (
input wire a, // 输入位a
input wire b, // 输入位b
output wire sum, // 和
output wire carry // 进位
);
// 用连续赋值语句描述组合逻辑
assign sum = a ^ b; // 异或运算
assign carry = a & b; // 与运算
endmodule
这段代码里,assign是连续赋值,表示sum和carry的值随a、b的变化立即更新。这就是组合逻辑的特点。
我的建议:初学者写模块时,先把端口定义写清楚。input、output、wire、reg这些关键字要搞明白。wire用于组合逻辑输出,reg用于时序逻辑(always块中赋值)。半加器是纯组合逻辑,所以用wire。
仿真测试一下:
module tb_half_adder;
reg a, b;
wire sum, carry;
half_adder uut (
.a(a),
.b(b),
.sum(sum),
.carry(carry)
);
initial begin
a = 0; b = 0; #10;
a = 0; b = 1; #10;
a = 1; b = 0; #10;
a = 1; b = 1; #10;
$finish;
end
initial begin
$monitor("a=%b b=%b sum=%b carry=%b", a, b, sum, carry);
end
endmodule
运行结果:
a=0 b=0 sum=0 carry=0
a=0 b=1 sum=1 carry=0
a=1 b=0 sum=1 carry=0
a=1 b=1 sum=0 carry=1
嗯,和真值表完全一致。这个半加器虽然简单,但它包含了Verilog模块的基本结构:端口定义、内部逻辑、例化方式。后面我们写全加器、ALU、甚至RISC-V内核,都是在这个基础上扩展。
本章小结:我们从晶体管讲到逻辑门,从组合逻辑讲到时序逻辑,最后用半加器完成了第一个Verilog模块。这些是数字IC设计的基石。后面章节我们会逐步深入,从半加器扩展到全加器,再到算术逻辑单元,最终搭建一个完整的RISC-V处理器内核。
公众号:蓝海资料掘金营,微信deep3321