2. 数字逻辑基础回顾:组合逻辑与时序逻辑、触发器与寄存器、时钟与复位、有限状态机
各位同学,在正式进入 RISC-V 流水线设计之前,咱们得先把数字逻辑的底子夯实了。你想想看,五级流水线说白了就是一堆组合逻辑和时序逻辑的巧妙组合。如果这些基本概念没吃透,后面遇到时序违例、亚稳态这些问题,你会很头疼的。
我个人习惯,每次开始一个新项目,都会先画一张数字逻辑的思维导图。今天我就带大家快速过一遍这些核心概念,顺便分享一些我在实际项目中踩过的坑。
2.1 组合逻辑 vs 时序逻辑
先说说组合逻辑。它的输出只取决于当前的输入,没有记忆功能。比如一个简单的与门,输入 A 和 B 都是 1,输出就是 1;输入变了,输出立刻跟着变。
时序逻辑就不一样了。它的输出不仅取决于当前输入,还取决于电路之前的状态。说白了,它有“记忆”。触发器就是最典型的时序逻辑单元。
| 特性 | 组合逻辑 | 时序逻辑 |
|---|---|---|
| 输出依赖 | 仅当前输入 | 当前输入 + 历史状态 |
| 记忆能力 | 无 | 有(靠触发器) |
| 典型电路 | 加法器、多路选择器、译码器 | 计数器、寄存器、状态机 |
| 时钟依赖 | 不依赖 | 依赖时钟沿 |
| 延迟特性 | 门延迟(传播延迟) | 时钟到输出延迟(Tco) |
我在项目中遇到过一个问题:一个同学把组合逻辑的反馈环路当成了时序逻辑来用,结果仿真怎么都不对。记住,组合逻辑的反馈会形成锁存器(Latch),这在大多数 ASIC 设计中是要避免的。
2.2 触发器与寄存器
触发器(Flip-Flop)是时序逻辑的基石。我们最常用的是 D 触发器,它在时钟上升沿采样输入 D,然后输出 Q 保持这个值直到下一个时钟沿。
寄存器呢?其实就是一组 D 触发器并联在一起,共享同一个时钟和复位信号。比如一个 32 位的寄存器,就是 32 个 D 触发器排排坐。
来看看 D 触发器的 Verilog 描述:
// 带异步复位的 D 触发器
module d_flip_flop (
input wire clk,
input wire rst_n,
input wire d,
output reg q
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
q <= 1'b0;
else
q <= d;
end
endmodule
注意看,我用了非阻塞赋值 <=。这是时序逻辑的标准写法。如果用阻塞赋值 =,仿真行为会完全不同,综合出来的电路也可能不是你想要的。
2.3 时钟与复位
时钟是数字系统的“心跳”。所有时序逻辑都跟着时钟沿走。时钟的频率决定了系统能跑多快,时钟的抖动和偏斜直接影响时序裕量。
复位呢?它让电路回到一个已知的初始状态。常用的复位方式有两种:
- 异步复位: 复位信号不依赖时钟,只要复位有效,输出立刻清零。优点是响应快,缺点是对复位信号的毛刺敏感。
- 同步复位: 复位信号只在时钟沿有效时起作用。优点是抗干扰能力强,缺点是需要复位信号保持足够长的时间。
我曾经在一个项目中,因为异步复位信号没有做同步处理,导致芯片在某个极端工况下出现了复位失败。从那以后,我养成了一个习惯:所有异步复位信号进入模块之前,必须用两级触发器做同步。
时钟和复位的关系,可以用下面这张图来理解:
2.4 有限状态机
有限状态机(FSM)是数字设计里最强大的工具之一。它让电路能够“记住”自己处在什么状态,并根据输入决定下一步去哪。
FSM 分两种:
- Moore 型: 输出只取决于当前状态。比如一个简单的计数器,输出就是当前计数值。
- Mealy 型: 输出取决于当前状态和输入。比如一个序列检测器,检测到特定序列时输出才拉高。
我个人习惯,在 RISC-V 流水线控制器的设计中,大部分用 Moore 型。为什么?因为 Moore 型的输出更稳定,不会因为输入毛刺而抖动。但 Mealy 型在某些场景下更高效,比如需要快速响应的握手协议。
来看一个简单的 FSM 例子:一个 3 状态的 Moore 型状态机,检测输入序列 "101"。
// 序列检测器:检测 "101"
module seq_detector (
input wire clk,
input wire rst_n,
input wire din,
output reg dout
);
// 状态编码(独热码)
localparam IDLE = 3'b001,
S1 = 3'b010,
S2 = 3'b100;
reg [2:0] state, next_state;
// 状态寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= IDLE;
else
state <= next_state;
end
// 次态逻辑(组合逻辑)
always @(*) begin
next_state = IDLE; // 默认状态
case (state)
IDLE: next_state = din ? S1 : IDLE;
S1: next_state = din ? S1 : S2;
S2: next_state = din ? S1 : IDLE;
default: next_state = IDLE;
endcase
end
// 输出逻辑(Moore 型)
always @(*) begin
dout = (state == S2) ? 1'b1 : 1'b0;
end
endmodule
注意看,我把次态逻辑和输出逻辑分开了。这是我一直坚持的写法——三段式状态机。状态寄存器、次态逻辑、输出逻辑各写一个 always 块。这样代码清晰,综合出来的电路也规整,调试的时候一眼就能看出问题在哪。
好了,数字逻辑基础就回顾到这里。这些概念在后面的流水线设计中会反复用到。特别是状态机,RISC-V 的控制单元本质上就是一个复杂的状态机。把这些基础打牢,后面学起来会轻松很多。