2. 数字逻辑设计基础回顾:组合逻辑与时序逻辑、D触发器与时钟域、同步与异步复位、Verilog/VHDL快速入门
做FPGA加速,说白了就是在跟数字逻辑打交道。你想想看,不管是做市信号的生成,还是订单的快速处理,底层都是0和1的舞蹈。这一章,咱们把基础打牢。我见过不少新手,上来就写代码,结果时序跑不通,找半天bug,最后发现是最基本的逻辑概念没搞清。
2.1 组合逻辑 vs 时序逻辑
这两个概念,是数字设计的基石。我习惯这么区分:组合逻辑的输出,只取决于当前输入;时序逻辑的输出,还跟之前的状态有关。
举个例子,一个加法器就是组合逻辑。你输入A和B,它立马算出A+B。没有记忆,没有延迟(理想情况下)。而一个计数器,就是时序逻辑。它得记住当前计数值,下一个时钟沿来了,才加1。
核心区别一句话:
- 组合逻辑:无记忆,输出 = f(当前输入)
- 时序逻辑:有记忆,输出 = f(当前输入 + 历史状态)
在FPGA里,组合逻辑用查找表(LUT)实现,时序逻辑用触发器(FF)实现。我刚开始做项目时,总想把所有逻辑都塞进一个always块里,结果综合出来一堆LUT和FF混在一起,时序一塌糊涂。后来才明白,写代码前,先想清楚哪些是组合,哪些是时序,这是基本功。
2.2 D触发器与时钟域
D触发器,是时序逻辑的核心元件。它长什么样?说白了,就是一个存储单元。时钟上升沿(或下降沿)到来时,它把输入D的值,锁存到输出Q上。
它的真值表很简单:
| 时钟CLK | 输入D | 输出Q (下一时刻) |
|---|---|---|
| 上升沿 | 0 | 0 |
| 上升沿 | 1 | 1 |
| 非上升沿 | X | 保持不变 |
嗯,这里要注意:时钟域的概念。一个FPGA里可能有多个时钟,比如系统时钟200MHz,以太网时钟125MHz。不同时钟域的信号直接交互,就会出问题——这叫跨时钟域(CDC)问题。
避坑指南:我曾经在一个做市项目里,把行情时钟域的信号直接送到了订单处理时钟域。结果呢?偶尔出现亚稳态,导致订单价格错乱。那次回测亏了不少钱,才让我彻底重视CDC问题。跨时钟域一定要用同步器(两级或三级D触发器打拍),别偷懒。
2.3 同步复位 vs 异步复位
复位,就是把电路初始化到一个已知状态。FPGA里,复位有两种方式:
- 同步复位:只在时钟有效沿才检查复位信号。复位信号必须满足建立/保持时间。
- 异步复位:复位信号一有效,立刻复位,不受时钟控制。
我个人的习惯是:能用同步复位,就用同步复位。为什么?异步复位容易受毛刺干扰,而且复位释放时如果跟时钟沿太近,容易产生亚稳态。
但有些场景,异步复位确实方便。比如系统上电瞬间,时钟还没稳定,异步复位就能保证所有寄存器进入已知状态。
我的经验:如果你非要用异步复位,记得做异步复位、同步释放处理。也就是复位信号先经过两级D触发器同步,再送给各个模块。这样既保证了复位的即时性,又避免了亚稳态。
2.4 Verilog/VHDL快速入门
做FPGA,你得会一门硬件描述语言。我个人更偏爱Verilog,语法简洁,跟C语言有点像。VHDL更严谨,适合大型工程。这里我以Verilog为例,带你快速上手。
一个典型的Verilog模块长这样:
module counter (
input wire clk, // 时钟
input wire rst_n, // 异步复位,低有效
input wire en, // 使能
output reg [7:0] count // 8位计数器输出
);
// 时序逻辑:计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
count <= 8'd0; // 复位清零
else if (en)
count <= count + 1'b1; // 使能时加1
else
count <= count; // 保持
end
endmodule
你看,这个模块包含了:
- 端口声明:input/output,wire/reg类型
- always块:描述时序逻辑,敏感列表里有时钟和复位
- 非阻塞赋值 <= :时序逻辑里一定要用这个,别用=
VHDL的写法类似,但语法更啰嗦:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity counter is
Port ( clk : in STD_LOGIC;
rst_n : in STD_LOGIC;
en : in STD_LOGIC;
count : out STD_LOGIC_VECTOR (7 downto 0));
end counter;
architecture Behavioral of counter is
signal count_reg : unsigned(7 downto 0) := (others => '0');
begin
process(clk, rst_n)
begin
if rst_n = '0' then
count_reg <= (others => '0');
elsif rising_edge(clk) then
if en = '1' then
count_reg <= count_reg + 1;
end if;
end if;
end process;
count <= std_logic_vector(count_reg);
end Behavioral;
快速入门要点:
- Verilog用
always @(posedge clk)描述时序,VHDL用process(clk) - 组合逻辑用阻塞赋值
=,时序逻辑用非阻塞赋值<= - 千万别把
wire和reg搞混。简单记:wire是连线,reg是变量
好了,基础就这些。你想想看,这些概念是不是都很简单?但就是这些简单的东西,组合起来,就能做出复杂的做市系统。下一节,咱们开始真正进入FPGA加速的世界。
我的建议:学数字逻辑,别光看书。打开Vivado或Quartus,写几个小模块,看看RTL视图,跑跑仿真。实践出真知。我在带新人时,总让他们先写一个计数器,再写一个状态机,最后做一个简单的SPI接口。走完这三步,基础就扎实了。
好了,这一章就到这里。记住,基础不牢,地动山摇。做FPGA加速,这些概念会反复用到。下一章,咱们聊聊FPGA的硬件架构,看看里面到底有什么宝贝。