FPGA 与数字电路设计通关笔记
目录
- FPGA 硬件开发范式与思维转变
- 数字电路硬件基石与编码机理
- Verilog RTL 语法精要与编译器约束
- 组合逻辑隐性陷阱与工业级设计规范
- 典型电路建模实战:3-8 译码器
一、 FPGA 硬件开发范式与思维转变
FPGA 设计本质上是**“用代码画原理图”**,与 C++ 等软件开发有着根本性的区别。
| 维度 | 软件开发(C++) | 硬件开发(Verilog RTL) |
|---|---|---|
| 执行机制 | 顺序执行(Sequential) | 绝对并发(Concurrent,所有门电路同时上电工作) [1] |
| 例化本质 | 动态内存分配(在 RAM 堆栈上分配地址空间) | 静态硅片分配(在芯片上消耗具体的物理门/LUT资源) |
| 互连信号 | 变量(读取/写入内存) | 物理导线(Net,具有传播延迟 $t_{pd}$) [1] |
| 调试手段 | 打印信息(printf)、单步调试(Debug) | 仿真时序波形图(Waveforms,通过 GTKWave/ModelSim 观察) |
二、 数字电路硬件基石与编码机理
1. 为什么计算机和 FPGA 统一使用“补码”?
- 物理痛点:若使用“原码”或“反码”做减法,电路内部需要比较器、减法器、加法器和多路选择器,导致物理网表臃肿、时延大。
- 物理实现(补码减法):补码将减法转化为加法:$A - B = A + (-B) = A + \bar{B} + 1$。
- 极简硬件实现:在多位全加器中,只需将输入 $B$ 按位取反,并将最低位进位输入 $C_{in}$ 强制接高电平
1,即可直接复用同一个加法器完成减法,节省了大量硬件面积。
2. 异或门(XOR)的“受控反相器”物理机制
在逻辑运算(^)中,异或门是天然的控制门:
- 当控制端 $sel = 0$ 时:$y = b \oplus 0 = b$(数据原样输出);
- 当控制端 $sel = 1$ 时:$y = b \oplus 1 = \bar{b}$(数据按位取反,变为反码)。
- 应用:该电路作为补码加减法控制电路的核心,能动态选择是做加法还是做减法。
3. D 触发器(D-FF):时序逻辑与记忆的源头
- 快照机制:D 触发器是边沿敏感(Edge-Sensitive)器件。它只在时钟信号 CLK 从
0变到1(正边沿posedge)的一瞬间锁存输入 $D$ 的状态,并将该状态传送到输出 $Q$。 - 状态保持:在非上升沿期间,无论输入 $D$ 如何变化,输出 $Q$ 保持前一次快照的值不变。这是硬件存储和“记忆”的基础。
4. 寄存器(Register):D 触发器的并排与时钟同步
- 定义:为了存储多位宽的数据(如 8-bit
char或 32-bitint),硬件上将多个 DFF 并排排列。 - 共享时钟(Shared CLK):为了防止数据更新时产生**时钟偏斜(Clock Skew)**和数据“参差不齐”的混乱,必须让所有 DFF 共享同一个 CLK 信号源,以保证它们在物理上的同一个皮秒瞬时同步更新。
三、 Verilog RTL 语法精要与编译器约束
1. 模块结构(Module & Endmodule)
- 物理映射:一个
module对应一颗集成电路(IC)芯片的边界外壳。 - 端口(引脚)类型:
input:输入端口,信号只能单向流入,物理属性强制为线网型(wire)。output:输出端口,信号向外输出。inout:双向端口,多用于总线收发,内部需要高阻态(Z)逻辑进行物理控制。
2. 数据类型:wire(线网)与 reg(寄存器)
wire:代表物理连接导线,只能用 连续赋值语句assign驱动 [1]。其左值(LHS)必须是wire型 [1]。reg:在行为描述中用于锁存状态,必须用 过程赋值语句(在always/initial块中) 驱动 [1]。其左值必须是reg型 [1]。- 【物理误区】
reg不等于物理寄存器:- 在 组合过程块
always @(*)中,reg变量会被综合器映射为纯组合逻辑电路(导线和门) [1]; - 在 时序过程块
always @(posedge clk)中,reg变量才会被综合器映射为物理 D 触发器(DFF) [1]。
- 在 组合过程块
3. 子程序复用:function 与 task
| 维度 | 函数(function) |
任务(task) |
|---|---|---|
| 仿真延迟 | 严格为零延迟,禁止使用任何时延或事件控制(#, @, wait) [1.1.9] |
允许非零延迟,可以包含时效控制(常用于产生总线周期) [1.1.5] |
| 端口规则 | 至少一个 input;禁止使用 output / inout |
任意数量(包括零个)的 input, output, inout [1.1.5] |
| 可综合性 | 高度可综合,映射为纯组合逻辑电路 [1.1.1, 1.1.9] | 不含时延时可综合;若含时延则绝对不可综合(常用于 Testbench) [1.1.1, 1.1.9] |
| 应用场景 | 算术转换、CRC 计算、编码/译码器封装 | 仿真测试平台中的协议发送、总线读写行为建模 |
| 安全机制 | 并发调用时,可使用 automatic 关键字将其转为可重入的动态局部堆栈存储 [1.1.3, 1.1.5] |
4. 组合逻辑的两种描述范式
- 连续赋值
assign:- 用于数据流建模。只要右值(RHS)发生变化,仿真器便会激活该语句重新计算(右值敏感事件驱动),瞬时传递给左值(LHS)。
- 组合过程块
always @(*):- 用于行为级建模。括号中的
(*)表示隐式通配符敏感列表,编译器会自动将所有右值信号加入监听,彻底规避了因敏感信号漏写而产生非预期锁存器(Latch)的物理隐患 [1]。
- 用于行为级建模。括号中的
四、 组合逻辑隐性陷阱与工业级设计规范
1. 隐藏陷阱:意外锁存器(Unintended Latch)的引入
- 成因:在组合逻辑过程块(
always @(*))中,如果条件分支未覆盖全(如if缺少else,或者case缺少default),在条件未满足时,语法要求变量“保持旧值” [1]。由于组合逻辑门没有记忆功能,综合工具被迫插入一个电平敏感的锁存器(Latch) [1]。 - Latch 危害:
- 静态时序分析(STA)灾难:会引入组合反馈环路,导致时序分析路径不闭合,使时序收敛失败。
- 毛刺敏感:容易将组合电路的竞争冒险毛刺透传锁存,引发亚稳态。
2. 规避 Latch 的两大标准设计范式
- 规范一:分支完备法
- 在写组合逻辑时,确保
if必须配对else;case必须配对default[1]。
- 在写组合逻辑时,确保
- 规范二:默认赋值法(Default Assignment) —— 工业级最推荐写法
-
在
always @(*)块的最开头对被赋值信号赋予初始默认电平。根据阻塞赋值(=)的覆盖规则,后续即使分支条件不全,变量在任何情况下也都有确定值,从根本上完全杜绝 Latch 的生成:always @(*) begin y = 1'b0; // 默认赋值,规避一切 Latch 隐患 if (sel) begin y = a; end end
-
五、 典型电路建模实战:3-8 译码器
基于上述所有设计原则,标准的工业级 3-8 译码器实现如下:
// 3-8 译码器模块声明
module decoder3_8 (
input wire [2:0] sel, // 3位二进制输入编码网线
output reg [7:0] y // 8位独热码输出寄存器(由于在always块内部赋值,必须声明为reg型)
);
// 组合逻辑行为级描述(使用case平行译码选择网络,延迟对称)
always @(*) begin
case (sel)
3'd0: y = 8'b0000_0001; // 独热码特征:同一时间只有一位为1
3'd1: y = 8'b0000_0010;
3'd2: y = 8'b0000_0100;
3'd3: y = 8'b0000_1000;
3'd4: y = 8'b0001_0000;
3'd5: y = 8'b0010_0000;
3'd6: y = 8'b0100_0000;
3'd7: y = 8'b1000_0000;
// 因为 Verilog 信号具有四值逻辑(0, 1, X, Z),必须配齐 default
// 才能彻底防止 sel 在不定态(X)或高阻(Z)时触发意外 Latch
default: y = 8'b0000_0000;
endcase
end
endmodule
这份笔记包含了从 FPGA 开发工具链对比到基本组合/时序单元、一直到完整的 3-8 译码器实现的全部核心知识点。你可以将其作为日常查阅的手册。