结合前文《FPGA 高速数字信号处理》的核心观点,我将分 6 大典型场景 ,用 C 语言(CPU/MCU 串行实现) 和 Verilog(FPGA 硬件电路实现) 做逐行代码对比,覆盖:基础运算、FIR 滤波、乘法实现、流水线、数据延迟线、数据流架构。
每一组对比都会说明:执行逻辑、时钟周期、资源开销、性能差异 ,同时关联原文知识点、结合你当前电赛信号滤波 / 分离项目做落地解读。
前置约定
- CPU:通用处理器(ARM/PC/ 单片机),代码为标准 C 语言,串行指令执行;
- FPGA:Xilinx Artix-7,Verilog 描述硬件电路,并行 / 流水线执行;
- 时钟统一:系统时钟
100MHz(周期 10ns);- 数据位宽:统一使用 16bit 有符号数(电赛信号处理通用 Q1.15 格式)。
场景 1:基础运算 y = a * x + b
原文对应知识点
CPU 是指令串行执行 ;FPGA 是直接搭建硬件通路,单周期完成运算。
1.1 C 语言实现(CPU 串行)
c
运行
// CPU/单片机 实现 y = a * x + b
#include <stdint.h>
// 16bit 有符号数据
int16_t calc(int16_t a, int16_t x, int16_t b)
{
int16_t mul_res;
int16_t y;
// 步骤1:执行乘法(单独指令周期)
mul_res = a * x;
// 步骤2:执行加法(单独指令周期)
y = mul_res + b;
return y;
}
执行流程(CPU)
- 取指令 → 读取
a/x→ 执行乘法 → 写回中间值(至少 2 个时钟周期); - 读取中间值 + 读取
b→ 执行加法 → 写回结果(额外 1 个时钟周期); - 整体:至少 3 个时钟周期完成一次运算,存在指令调度、内存访问开销。
1.2 Verilog 实现(FPGA 硬件电路)
verilog
// FPGA 实现 y = a * x + b,纯组合电路,单周期输出
module basic_calc(
input wire clk,
input wire signed [15:0] a,
input wire signed [15:0] x,
input wire signed [15:0] b,
output reg signed [15:0] y
);
always @(posedge clk) begin
// 乘法、加法硬件直连,一周期完成
y <= a * x + b;
end
endmodule
硬件行为(FPGA)
综合后会生成:硬件乘法器 + 硬件加法器 直连电路。
- 输入
a/x/b同时进入电路; - 1 个时钟周期(10ns)直接输出结果;
- 无指令、无内存读写、无调度开销。
1.3 核心差异总结
表格
| 维度 | CPU (C 语言) | FPGA (Verilog) |
|---|---|---|
| 执行方式 | 串行指令,分步执行 | 硬件电路并行,通路直连 |
| 单次运算耗时 | ≥3 个时钟周期 | 1 个时钟周期 |
| 额外开销 | 指令取指、内存读写、调度 | 无额外开销 |
| 高速数据流适配 | 差,连续计算易拥堵 | 优,线速持续处理 |
场景 2:8 阶 FIR 滤波器(核心对比,原文重点案例)
FIR 公式: yn=h0xn+h1xn−1+h2xn−2+...+h7xn−7 h0∼h7 为滤波器系数,xn−k 为历史采样数据。
原文对应知识点
CPU 用循环串行计算 ;FPGA 例化多组乘法器 + 加法树,全并行计算。
2.1 C 语言实现(CPU 串行循环)
c
运行
// 8阶 FIR 滤波器 - CPU 串行实现
#include <stdint.h>
// FIR 系数(16bit 定点)
const int16_t h[8] = {128, 256, 512, 1024, 1024, 512, 256, 128};
// 历史数据缓冲区(保存最近8个采样点)
int16_t x_buf[8] = {0};
int16_t fir_8order(int16_t x_in)
{
int16_t y = 0;
int i;
// 1. 数据移位:更新历史缓冲区(串行移位)
for(i = 7; i > 0; i--)
{
x_buf[i] = x_buf[i-1];
}
x_buf[0] = x_in;
// 2. 串行循环:逐次乘加(循环8次)
for(i = 0; i < 8; i++)
{
y += h[i] * x_buf[i];
}
return y;
}
执行流程(CPU)
- 数据移位:循环 7 次,7 个周期;
- 乘累加:循环 8 次,每次
乘+加,至少 16 个周期; - 整体:二十多个时钟周期输出 1 个滤波结果;
- 新采样点必须等上一轮计算完成,吞吐率极低。
2.2 Verilog 实现(FPGA 全并行 FIR)
verilog
// 8阶 FIR 滤波器 - FPGA 并行架构
module fir_8order_parallel(
input wire clk,
input wire rst_n,
input wire signed [15:0] x_in,
output reg signed [15:0] y_out
);
// 1. 移位寄存器链:保存最近8个采样点 x[n]~x[n-7]
reg signed [15:0] x0, x1, x2, x3, x4, x5, x6, x7;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
{x0,x1,x2,x3,x4,x5,x6,x7} <= 0;
end else begin
x0 <= x_in;
x1 <= x0;
x2 <= x1;
x3 <= x2;
x4 <= x3;
x5 <= x4;
x6 <= x5;
x7 <= x6;
end
end
// 2. FIR 系数(16bit 定点)
localparam signed [15:0]
h0 = 16'd128, h1 = 16'd256, h2 = 16'd512, h3 = 16'd1024,
h4 = 16'd1024, h5 = 16'd512, h6 = 16'd256, h7 = 16'd128;
// 3. 并行乘法:8组乘法器同时工作(空间换时间)
wire signed [31:0] m0, m1, m2, m3, m4, m5, m6, m7;
assign m0 = x0 * h0;
assign m1 = x1 * h1;
assign m2 = x2 * h2;
assign m3 = x3 * h3;
assign m4 = x4 * h4;
assign m5 = x5 * h5;
assign m6 = x6 * h6;
assign m7 = x7 * h7;
// 4. 加法树:多级加法并行求和
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
y_out <= 0;
else
y_out <= m0 + m1 + m2 + m3 + m4 + m5 + m6 + m7;
end
endmodule
硬件行为(FPGA)
- 移位寄存器:1 个周期完成所有数据更新(并行打拍,不是循环移位);
- 8 组乘法器:同一时钟周期并行完成全部乘法;
- 加法树:并行求和;
- 整体:每 1 个时钟周期输出 1 个滤波结果,满速吞吐。
2.3 核心差异总结
表格
| 维度 | CPU (C 语言) | FPGA (并行 Verilog) |
|---|---|---|
| 计算方式 | 循环串行,逐次运算 | 多组硬件单元并行运算 |
| 单帧耗时 | 二十 + 时钟周期 | 1 个时钟周期 |
| 吞吐率 | 低,无法处理高速数据流 | 满速,100MHz 线速运行 |
| 资源开销 | 仅软件内存,无硬件成本 | 消耗多个乘法器、寄存器 |
结合你的电赛项目:你使用的 Vivado FIR IP 内部就是该并行 + 加法树架构,这也是 FPGA 能实时分离 50k/100kHz 信号的核心原因。
场景 3:乘法 / 乘累加实现:LUT 软乘法 VS DSP48E1 硬核
原文对应知识点
FPGA 优先使用 DSP48E1 专用硬核 做乘加运算,比 LUT 拼接的软乘法速度更快、资源更少、功耗更低。
3.1 方式 A:普通 * 运算符(LUT 拼接软乘法)
Verilog 中直接写 a*b,综合器会用 LUT+FF 拼接乘法器,不调用 DSP。
verilog
// LUT 软乘法(不使用DSP硬核)
module mul_lut(
input wire clk,
input wire signed [15:0] a,
input wire signed [15:0] b,
output reg signed [31:0] res
);
always @(posedge clk) begin
res <= a * b; // 综合为 LUT 组合逻辑乘法
end
endmodule
缺陷
- 速度上限:约 100MHz;
- 资源:占用数百个 LUT/FF;
- 时序差,高阶滤波极易违规。
3.2 方式 B:直接调用 DSP48E1 原语(官方推荐硬核)
对应原文给出的 DSP 乘累加 A*B+C 代码,FPGA 算力核弹。
verilog
// 调用 Xilinx DSP48E1 硬核:实现 MAC = A*B + C
module mul_dsp48e1(
input wire clk,
input wire rst,
input wire [24:0] A,
input wire [17:0] B,
input wire [47:0] C,
output wire [47:0] P
);
// DSP48E1 原语配置:OPMODE = 0001101 → A*B + C
DSP48E1 #(
.A_INPUT("DIRECT"),
.B_INPUT("DIRECT"),
.OPMODE(6'b0001101)
) dsp_mac_inst (
.CLK(clk),
.RST(rst),
.A(A),
.B(B),
.C(C),
.P(P),
.CEA1(1'b1), .CEA2(1'b1),
.CEB1(1'b1), .CEB2(1'b1),
.CEC(1'b1), .CEP(1'b1)
);
endmodule
优势
- 速度上限:600MHz+;
- 资源:仅占用 1 个 DSP Slice,几乎不消耗 LUT;
- 专为滤波、FFT 乘加运算优化,电赛高速 DSP 必用。
3.3 核心差异总结
表格
| 维度 | LUT 软乘法 | DSP48E1 硬核乘法 |
|---|---|---|
| 最高主频 | ~100MHz | 600MHz+ |
| 逻辑资源 (LUT/FF) | 大量占用 | 几乎不占用 |
| 适用场景 | 低速简易运算 | FIR/FFT/DDS 高速乘加(主力) |
电赛建议:手写滤波 / 乘加代码时,优先调用 DSP 原语 ,不要依赖
*运算符。
场景 4:流水线架构:无流水线 VS 多级流水线
原文对应知识点
流水线切割长组合逻辑,提升主频、拉高持续吞吐;仅增加单数据延迟,不影响连续数据流。
以 y = (in * coeff) + bias 三级运算为例对比。
4.1 无流水线(长组合逻辑)
所有运算在同一级组合逻辑完成,关键路径长,主频上不去。
verilog
// 无流水线:单级组合逻辑
module no_pipeline(
input wire clk,
input wire signed [15:0] in,
input wire signed [15:0] coeff,
input wire signed [15:0] bias,
output reg signed [15:0] out
);
always @(posedge clk) begin
// 乘法+加法 全部在一级逻辑内完成
out <= in * coeff + bias;
end
endmodule
问题
组合逻辑路径过长,综合后最高主频被限制(Artix-7 通常 <150MHz)。
4.2 三级流水线(寄存器切割路径)
插入寄存器分为三级:输入锁存 → 乘法 → 加法输出。
verilog
// 三级流水线架构(原文标准示例)
module pipeline_3stage(
input wire clk,
input wire signed [15:0] in,
input wire signed [15:0] coeff,
input wire signed [15:0] bias,
output reg signed [15:0] out
);
// 流水线寄存器
reg signed [15:0] stage1_reg; // 第1级:输入锁存
reg signed [31:0] stage2_reg; // 第2级:乘法结果
always @(posedge clk) begin
stage1_reg <= in; // 工位1:采集数据
stage2_reg <= stage1_reg * coeff; // 工位2:乘法运算
out <= stage2_reg + bias; // 工位3:加法输出
end
endmodule
硬件行为
- 第 1 拍:数据进入 stage1;
- 第 2 拍:数据进入 stage2;
- 第 3 拍:第一个有效结果输出;
- 从第 3 拍开始,每一拍持续输出新结果;
- 组合逻辑被拆分,主频可轻松跑到 250MHz+。
4.3 核心差异总结
表格
| 维度 | 无流水线 | 三级流水线 |
|---|---|---|
| 单数据延迟 (Latency) | 1 个周期 | 3 个周期(延迟增加) |
| 持续吞吐率 | 一般,主频受限 | 极高,满速线速运行 |
| 最高运行主频 | 低 (<150MHz) | 高 (>250MHz) |
结合你的项目:FIR/FFT IP 默认开启流水线,是滤波能实时处理高频信号的关键。
场景 5:滤波器延迟线:寄存器移位链 VS BRAM 环形缓冲区
原文对应知识点
高阶滤波禁止使用超长寄存器链,BRAM 环形缓冲区 是工程标准方案,节省资源、布线简单。
以 1024 阶 FIR 延迟线为例。
5.1 方式 A:寄存器数组移位(低效,不推荐)
verilog
// 错误写法:1024阶延迟线 - 纯寄存器移位链
reg [15:0] delay_line [1023:0]; // 1024个寄存器
always @(posedge clk) begin
// 循环移位:消耗大量FF + 布线爆炸
for(int i = 1023; i > 0; i = i-1) begin
delay_line[i] <= delay_line[i-1];
end
delay_line[0] <= x_in;
end
缺陷
- 占用 1024+ 触发器,资源爆满;
- 大规模寄存器链布线复杂,时序必违规;
- 阶数越高,问题越严重。
5.2 方式 B:BRAM 环形缓冲区(工程标准写法)
利用 FPGA 片上 BRAM 做循环缓存,几乎不消耗逻辑资源。
verilog
// 高效写法:BRAM 环形缓冲区(1024点延迟线)
module bram_delay_line(
input wire clk,
input wire rst_n,
input wire [15:0] din,
output wire [15:0] dout
);
localparam DEPTH = 1024;
reg [9:0] wr_ptr; // 写指针(循环地址)
// 1. 循环写指针(0~1023 循环)
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
wr_ptr <= 0;
else
wr_ptr <= (wr_ptr == DEPTH-1) ? 10'd0 : wr_ptr + 1'd1;
end
// 2. 例化 单端口BRAM(Vivado BRAM IP)
bram_1024x16 u_bram(
.clka(clk),
.wea(1'b1), // 始终使能写
.addra(wr_ptr),
.dina(din),
.addrb(wr_ptr - 10'd7), // 读取历史抽头数据
.doutb(dout)
);
endmodule
优势
- 仅占用 1 块 BRAM,FF/LUT 几乎无消耗;
- 地址指针循环读写,支持任意高阶滤波器;
- 布线简单,时序稳定。
5.3 核心差异总结
表格
| 维度 | 寄存器移位链 | BRAM 环形缓冲区 |
|---|---|---|
| 资源占用 | 大量 FF | 仅 1 块 BRAM |
| 布线 / 时序 | 差,高阶必违规 | 优,时序稳定 |
| 可拓展阶数 | 低(<100 阶) | 极高(数千阶) |
结合你的项目:DDS 波形表、高阶 FIR、图像行缓存,全部基于 BRAM 实现。
场景 6:跨时钟域数据流(异步 FIFO 对比)
原文对应知识点
不同时钟域(如 ADC/125MHz 网口时钟 + 100MHz 系统时钟)必须用 异步 FIFO 隔离,防止亚稳态。
6.1 错误写法:直接跨时钟域连线(亚稳态根源)
verilog
// 错误:两个异步时钟直接传数据,产生亚稳态
wire [15:0] adc_data; // ADC时钟:125MHz
reg [15:0] sys_data; // 系统时钟:100MHz
always @(posedge sys_clk) begin
sys_data <= adc_data; // 异步时钟直连 → 毛刺、功能不稳定
end
6.2 正确写法:异步 FIFO 隔离时钟域(工程标准)
verilog
// 正确:异步FIFO 隔离 125MHz(ADC) ↔ 100MHz(系统)
async_fifo u_fifo(
.wr_clk(adc_clk), // 写时钟:ADC 125MHz
.rd_clk(sys_clk), // 读时钟:系统 100MHz
.din(adc_data),
.dout(sys_data),
.wr_en(1'b1),
.rd_en(1'b1)
);
结合你的项目:如果后续使用「外部模拟加法器 + ADC 采集」方案,必须加异步 FIFO。
全局总结 & 电赛落地规则
一、CPU vs FPGA 核心代码思维差异
- C 语言 (CPU) :描述执行步骤,串行循环、指令驱动,适合低速、复杂业务逻辑;
- Verilog(FPGA) :描述硬件电路 ,并行、流水线、存储分级,适合高速实时信号处理。
二、电赛 FPGA 信号处理代码编写准则(直接套用)
- 乘加运算:优先调用 DSP48E1 原语 ,少用普通
*运算符; - 高阶延迟线:用 BRAM 环形缓冲区,禁止超长寄存器移位链;
- 高速数据流:必须加 流水线寄存器,提升主频与吞吐;
- 跨时钟域:大批量数据用 异步 FIFO,单路信号用两级打拍同步;
- 通用算法(FIR/FFT/DDS):优先使用 Vivado 官方 IP,不手写复杂算法;
- 定点运算:全程管控位宽,关键节点加饱和截位,防止波形溢出失真。
三、选型速记
- 低速、简单逻辑 → 普通组合逻辑 + 寄存器;
- 高速乘加、滤波 → DSP48E1 + 流水线;
- 大数据缓存、长延迟 → BRAM;
- 跨时钟域数据流 → 异步 FIFO。