第三章 硬件描述语言基础:组合逻辑与时序逻辑
各位同学,今天我们来聊聊硬件描述语言的基础。说实话,Verilog 和 VHDL 这东西,刚接触时觉得就是写代码,但写多了你会发现——它跟软件完全是两码事。
我刚开始做通信算法加速时,就犯过一个低级错误:把组合逻辑和时序逻辑混在一起,结果仿真怎么都对,上板就崩。后来被老工程师骂了一顿,才真正理解这两者的区别。
3.1 组合逻辑 vs 时序逻辑
组合逻辑,说白了就是输入一变,输出立刻变。没有记忆,没有时钟。比如一个加法器:a + b,结果瞬间出来。
时序逻辑就不一样了。它靠时钟驱动,输出只在时钟沿变化。寄存器、计数器、状态机,都是时序逻辑。
我个人的习惯是:能用时序逻辑的地方,绝不用组合逻辑。为什么?因为组合逻辑容易产生毛刺,时序逻辑天然抗干扰。
核心区别一句话:
- 组合逻辑:输入决定输出,无记忆
- 时序逻辑:时钟决定输出,有记忆
来看个简单的 Verilog 例子:
// 组合逻辑:assign 语句
assign sum = a + b;
// 时序逻辑:always @(posedge clk)
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
sum_reg <= 0;
else
sum_reg <= a + b;
end
注意看,组合逻辑用 assign,时序逻辑用 always @(posedge clk)。这个区别,你在写代码时一定要刻在脑子里。
3.2 状态机设计
状态机是通信算法加速的核心。你想想看,一个数据包进来,要经过解析、处理、输出,每一步都是一个状态。
我常用的状态机风格是 三段式。为什么?因为好维护,好调试。
三段式状态机结构:
- 第一段:时序逻辑,描述状态跳转
- 第二段:组合逻辑,描述下一状态
- 第三段:时序逻辑,描述输出
举个例子,一个简单的通信协议解析状态机:
// 状态定义
localparam IDLE = 2'b00;
localparam HEADER = 2'b01;
localparam PAYLOAD = 2'b10;
localparam CHECKSUM = 2'b11;
// 第一段:状态跳转
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 = start_flag ? HEADER : IDLE;
HEADER: next_state = header_done ? PAYLOAD : HEADER;
PAYLOAD: next_state = payload_done ? CHECKSUM : PAYLOAD;
CHECKSUM:next_state = checksum_done ? IDLE : CHECKSUM;
default: next_state = IDLE;
endcase
end
// 第三段:输出逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
data_valid <= 1'b0;
else if (state == PAYLOAD && payload_done)
data_valid <= 1'b1;
else
data_valid <= 1'b0;
end
我的经验:三段式状态机最大的好处是——出问题了,你一眼就能看出是状态跳转错了,还是输出逻辑错了。我曾经用两段式写过,结果调试了三天,最后发现是组合逻辑的竞争问题。
3.3 流水线设计思想
流水线,说白了就是「把一个大任务拆成多个小任务,每个小任务用一级寄存器隔开」。这样做的目的是——提高时钟频率。
你想想看,一个复杂的组合逻辑,从输入到输出可能要经过 10 级门,延迟很大。如果把它拆成 5 级流水,每级只做 2 级门的工作,那时钟频率就能提高 5 倍。
在通信算法中,流水线用得特别多。比如 FIR 滤波器、FFT、信道估计,几乎都是流水线结构。
来看一个简单的 3 级流水线加法树:
// 输入:a[7:0], b[7:0], c[7:0], d[7:0]
// 输出:sum
reg [8:0] sum1, sum2;
reg [9:0] sum3;
// 第1级流水
always @(posedge clk) begin
sum1 <= a + b;
sum2 <= c + d;
end
// 第2级流水
always @(posedge clk) begin
sum3 <= sum1 + sum2;
end
// 第3级流水(输出)
always @(posedge clk) begin
sum <= sum3;
end
这个例子很简单,但体现了流水线的核心思想:每级只做一点点事,用寄存器把结果锁存起来。
注意:流水线不是万能的。它有三个代价:
- 延迟增加:N 级流水,延迟增加 N 个时钟周期
- 面积增加:每级都要加寄存器
- 控制复杂:流水线冲突、数据依赖都要处理
我曾经在一个项目中,为了追求高频,把流水线做到了 20 级。结果控制逻辑比计算逻辑还复杂,最后不得不回退到 8 级。
3.4 知识体系总览
下面这张图,是我自己总结的本章知识体系。你可以把它当作一个「地图」,学完本章后对照着检查自己是否掌握了每个点。
嗯,这张图把本章的三个核心模块串起来了。你仔细看,它们之间是有联系的:状态机里的状态跳转是时序逻辑,输出可以是组合逻辑;流水线的每一级也是时序逻辑。说白了,时序逻辑是骨架,组合逻辑是血肉,状态机是大脑,流水线是效率。
本章要点回顾:
- 组合逻辑:assign、always @(*),无记忆,易毛刺
- 时序逻辑:always @(posedge clk),有记忆,抗干扰
- 状态机:三段式最推荐,好调试好维护
- 流水线:拆大任务为小任务,提高时钟频率,但增加延迟和面积
最后说一句:这些基础东西,看起来简单,但真正用好需要大量实践。我见过太多人,状态机写成一锅粥,流水线做成面条。别急,慢慢来,多写多仿真,自然就熟了。
公众号:蓝海资料掘金营,微信deep3321