1. RTL设计基础回顾
这章咱们不搞花架子,直接回顾RTL设计的几个核心地基。我个人做验证和物理设计这些年,发现很多后期翻车其实都源自最基础的概念没吃透。你想想看,逻辑门、触发器、状态机这些老伙计,要是理解不到位,后续的时序收敛、面积优化全是扯淡。
好,咱们开始一个个过,带着我踩过的坑一起看。
1.1 数字逻辑基础——别只看符号,要看行为
数字逻辑说白了就是0和1的天下。但我觉得很多新手把问题想太简单了。比如一个简单的与门:
assign y = a & b;
这行代码背后其实包含了传播延迟、毛刺、驱动强度这些物理问题。我在一个高速接口项目中遇到过,就是因为忽略了一个简单与门的组合路径延迟,导致后仿时序违例。
| 逻辑门 | 符号 | Verilog表达 | 常见陷阱 |
|---|---|---|---|
| 与门 | & | y = a & b | 多驱动时产生X态 |
| 或门 | | | y = a | b | 扇出过大导致延迟剧增 |
| 非门 | ~ | y = ~a | 与时钟配合不当产生毛刺 |
| 异或门 | ^ | y = a ^ b | 被当成X传播源 |
1.2 组合逻辑——组合不等于杂乱
组合逻辑的输出只依赖当前输入,没有记忆。听起来简单?但实际项目中组合逻辑的“糊墙”现象见过太多次了。说白了就是不管什么信号全往always块里塞,结果综合出一堆latch。
来看一个经典的组合逻辑例子:
always @(*) begin
case (sel)
2'b00: out = a + b;
2'b01: out = a - b;
2'b10: out = a & b;
default: out = '0;
endcase
end
注意啊,@(*) 触发条件一定要覆盖所有输入信号。我曾经见过有人漏掉sel本身,导致仿真和综合结果不一致。
组合逻辑设计的几个要点,我总结成清单:
- 任何组合路径必须从寄存器输出到寄存器输入,不要穿过多个组合块再直接接IO。
- 避免冗长的组合链(深度超过10级),否则时序很难收敛。
- case语句务必写全分支或加default,避免产生latch。
1.3 同步时序——时钟是设计的灵魂
同步时序逻辑靠时钟驱动,所有状态变化发生在时钟边沿。这个大家都懂,但我想说的是:时钟域设计才是真正的分水岭。
先看一个最简单的D触发器描述:
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
q <= 1'b0;
else
q <= d;
end
这里大家可能觉得不就是清零赋值吗?但我建议你注意异步复位和同步复位的区别。异步复位在复位信号有效时立刻置零,不受时钟约束;同步复位必须在时钟沿才能生效。我在一个低功耗项目中选择了异步复位,结果综合后复位网络产生了巨大的毛刺,差点导致芯片死机。
同步时序设计原则(我自己一直贴在工位上的):
- 所有触发器必须在同一个时钟域内,除非做跨时钟域同步。
- 时钟信号不能作为普通数据使用,必须由专用PLL或时钟模块驱动。
- 每个always块只描述一个功能(要么纯组合,要么纯时序),不要混合。
一个很容易被忽略的点:同步复位虽然更安全,但会消耗更多的时序路径资源。如果你是做低功耗设计,可以折中采用异步复位、同步释放的方式。
1.4 状态机——三段的优雅与实战
状态机是数字设计的灵魂。我个人最推崇三段式状态机,因为它把组合逻辑、时序逻辑和输出逻辑彻底分开,可读性和可维护性都很好。看图:
你看,三段式结构非常清晰:
- 第一段(组合):根据当前状态和输入,计算出下一状态。
- 第二段(时序):在时钟沿把下一状态锁存为当前状态。
- 第三段(组合):根据当前状态(或者状态+输入)产生输出。
我写一个Moore型状态机的标准模板给大家参考:
module fsm_moore (
input clk, rst_n,
input [1:0] in,
output reg [1:0] out
);
reg [2:0] state, next_state;
parameter IDLE = 3'b001, RUN = 3'b010, DONE = 3'b100;
// 第一段:时序逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) state <= IDLE;
else state <= next_state;
end
// 第二段:组合逻辑—下一状态
always @(*) begin
case (state)
IDLE: next_state = (in == 2'b01) ? RUN : IDLE;
RUN: next_state = (in == 2'b10) ? DONE : RUN;
DONE: next_state = IDLE;
default: next_state = IDLE;
endcase
end
// 第三段:组合逻辑—输出
always @(*) begin
case (state)
IDLE: out = 2'b00;
RUN: out = 2'b01;
DONE: out = 2'b10;
default: out = 2'b00;
endcase
end
endmodule
1.5 再唠叨两句
回顾完这些基础,你可能会觉得:这都老掉牙了。但我在做RTL布局CI验证的时候,每次遇到综合后面积异常、时序违例、仿真对不上,最后追根溯源十有八九都跟这些基础概念有关系。比如组合逻辑里的latch、状态机未覆盖全的case、异步复位没处理好等等。
所以,把基础打牢,后面咱们讲CI流水线、物理设计约束才有底气。嗯,这是第一章,内容不多,但都很关键。后面几章我们开始搭完整的RTL布局CI环境,到时候你会感谢今天花时间回顾的这些老伙计。