储备知识:
我们在前文讲了BP神经网络算法和激活函数
BP神经网络算法
https://ee.ac.cn/index.php/archives/612.html
激活函数讲解
https://ee.ac.cn/index.php/archives/617.html
一、背景:为什么要在FPGA上跑神经网络?
在无源物联网(Battery-free IoT)和反向散射通信(Backscatter)场景中,传统的信号检测方法(如能量检测)虽然电路简单,但无法区分同频段的多种信号,且受噪声影响大;而基于PC或GPU的深度学习方案虽然精度高,但功耗动辄数十瓦,显然无法部署在依赖能量采集的微瓦级标签上。
核心矛盾在于:缺乏一种检测精度高、功耗极低(毫瓦级)、且能实时处理的解决方案。
答案是将轻量级的BP神经网络部署到低功耗FPGA上,在边缘端完成信号推理。
二、系统整体架构
整个检测系统的链路如下:
目标信号 → 天线 → 阻抗匹配 → LNA放大 → 包络检波(ENV) → ADC采样(1MSPS, 12bit) → FPGA(BP神经网络推理) → 调制控制信号在FPGA内部,BP神经网络负责识别当前输入的512点采样数据是否为LoRa信号的前导码(Preamble)。一旦识别成功,输出控制信号,开启后端的Backscatter调制模块。
FPGA选型:Actel IGLOO/e系列 AGLE600V5-FG484。选择它的原因是:
- Flash架构:上电即运行,无需外部配置芯片,适合无源系统;
- 超低功耗:静态功耗仅0.046mW,适合能量采集场景;
- 资源适中:13824个逻辑单元,足以容纳轻量级网络。
三、神经网络模型设计:极致轻量
为了在FPGA上高效部署,网络结构必须足够"轻"。本文设计的网络结构如下:
| 层级 | 神经元数 | 说明 |
|---|---|---|
| 输入层 | 512 | 对应ADC采集的一条信号的512个采样点 |
| 隐藏层 | 2 | 仅2个神经元,大幅降低计算量 |
| 输出层 | 1 | 二分类:是目标信号(1) / 非目标信号(0) |
激活函数:隐藏层和输出层均采用 ReLU。
$$f(x) = \max(0, x)$$
选择ReLU的原因不仅是训练效果好,更重要的是硬件实现极其简单——只需一个比较器判断正负,无需计算指数或乘法,这对FPGA非常友好。

训练环境:TensorFlow/Keras,使用Adam优化器和MSE损失函数。在真实环境数据集上训练后,测试集识别准确率可达 95.76%。

四、FPGA硬件实现详解
FPGA内部的神经网络是一个纯推理引擎。本节按数据流方向,逐个拆解每个模块的设计思路和Verilog实现要点。
整体RTL结构如下:

4.1 串并转换模块(Serial-to-Parallel

ADC(ADS7042)在片选信号CS拉低后,先输出2个前导0,再输出12位有效数据(D11→D0),每个数据位占用一个CLK周期。
设计要点:
- CS和CLK由FPGA内部产生,CS每16个CLK周期拉低一次;
- 在CS下降沿开始,用计数器对串行数据移位寄存;
凑齐12位有效数据后,输出一组并行数据
adc_data[11:0]。
波形图
module serial_p(input data0_in,input cs,input clk,output[12:0] data0_out,input rst); reg[11:0] temp; reg[12:0] save; integer count; integer cnt; always @(negedge clk or negedge rst) begin if(!rst) begin//reset temp<=12'b000000000000; count<=-1; save<=12'b111111111111; cnt<=-1; end else if(cs&&count%16==0) begin save<={1'b0,temp}; count<=1; cnt<=cnt+1; if(cnt==512) begin cnt<=1; end end else if(cs) begin temp<=12'b0; count<=1; end else if(count<3) count<=count+1; else begin temp<={temp,data0_in}; count<=count+1; end end assign data0_out=save; endmodule
4.2 数据归一化模块
问题:训练时,输入数据被归一化到 [0, 1.8](对应ADC参考电压1.8V)。如果在FPGA中直接用除法做 Vin = adc_data / 4095 * 1.8,会消耗大量逻辑资源(FPGA做除法器代价极高)。
解决方案:在软件端完成归一化,FPGA端只做整数运算。
具体做法:
- 在Python/Keras训练时,将输入数据
x归一化为x_norm = x / 4095 * 1.8; - 在提取权值时,将第一层权值
w除以1.8并放大1024倍取整; 这样FPGA输入的原始ADC值
adc_data(范围0~4095)直接与预处理后的权值相乘,等效于完成了归一化+加权。# 创建一个自定义的回调对象 cb = MyCallback() # 以只读方式打开训练数据文件 with open('/content/work/data/data.txt') as traindata: # 读取文件内容 data = traindata.read() # 将文件内容转换为numpy数组,并将数据类型转换为float,并将数组形状调整为(47100, 2) data = np.array(list(data.split()), dtype=float).reshape((47100, 2)) # 创建一个最大最小归一化对象 min_max_scaler = preprocessing.MinMaxScaler() # 归一化 data = data * (1/65535) # 以只读方式打开训练标签文件 with open('/content/work/data/label.txt') as labda: # 读取文件内容 labels = labda.read() # 将文件内容转换为numpy数组,并将数据类型转换为float,并将数组形状调整为(47100, 2) labels = np.array(list(labels.split()), dtype=float).reshape((47100, 2)) # 对data和labels进行乱序,保持它们的对应关系 # 为了保持对应关系,需要先将data和labels沿着第二个维度拼接起来,形成一个(100, 4)的数组 # 然后对这个数组进行乱序,再将它们沿着第二个维度分开,还原成data和labels data_labels = np.concatenate((data, labels), axis=1) np.random.shuffle(data_labels) data = data_labels[:, :2] labels = data_labels[:, 2:]FPGA端实现:
实际上,归一化模块在FPGA中被合并到了权值预处理阶段,硬件上不需要额外的归一化电路。但为了模块化清晰,也可以显式设计一个"电压映射"模块,将12bit ADC码值转换为定点数格式:
工程建议:永远不要在FPGA里做除法。所有线性变换(归一化、缩放)都应在软件端合并到权值中,FPGA只做整数乘加。
4.3 隐藏层神经元模块(核心计算单元)
隐藏层的数学表达式:
$$y = \text{ReLU}\left( \sum_{i=1}^{512} x_i \cdot w_i + b \right)$$ , i = 1..512
工程难点:
- 512次乘累加如果在一个时钟周期完成,需要512个乘法器,资源爆炸;
- 如果纯串行完成,需要512个时钟周期,一条chirp要512×512=262144个周期,太慢。
解决方案:采用 串行MAC流水线 + 双缓冲FIFO 架构。
设计思路:
- 权值存储:512个权值和1个偏置在编译时以
reg [31:0] w[0:511]形式固化在FPGA中; - FIFO缓存:ADC每输出一个采样点,立即写入FIFO。FIFO深度设为16即可(只要保证不溢出),因为读出速度(MAC运算)和写入速度(ADC采样)基本匹配;
- 串行MAC:每个时钟周期从FIFO读出一个数据
xi,与对应的wi相乘,累加到累加器acc; - 512个周期后:累加器结果加上偏置
b,送入ReLU模块。

- 位宽设计:输入22bit × 权值32bit = 乘法结果54bit,累加512次需要额外9bit,因此累加器至少需要 63bit。如果位宽不够,累加会溢出,导致结果完全错误。
- 时序约束:乘法器和加法器要满足16MHz时钟的建立保持时间。如果组合逻辑延迟太大,需要插入流水线寄存器(将乘法器和加法器分两级完成)。
- 双神经元并行:隐藏层有2个神经元,需要例化两个上述模块,各自独立的权值存储和累加器,但共享同一个FIFO输入。
4.4 ReLU激活函数模块
module relu (
input wire clk,
input wire rst_n,
input wire [31:0] x_in, // MAC+bias结果
input wire x_valid,
output reg [31:0] y_out,
output reg y_valid
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
y_out <= 32'd0;
y_valid <= 1'b0;
end else begin
y_valid <= x_valid;
if ($signed(x_in) >= 0)
y_out <= x_in;
else
y_out <= 32'd0;
end
end
endmodule4.5 输出层神经元模块
输出层只有1个神经元,输入是隐藏层2个神经元的输出:
$$\text{output} = \text{ReLU}\left( h_1 \cdot w'_1 + h_2 \cdot w'_2 + b' \right)$$
module nerual_out( clk, rst,x1,x2,data_out );
input clk, rst;
input signed[0:49] x1,x2;
output signed[79:0] data_out;
//<statements>
//reg signed[0:28] weight[0:1];
reg signed[0:7] save,save1,save2;
localparam bais=-50'd214979034352654;
//localparam bais = -26'd6406874;
localparam weight0 = 29'd22506522;
localparam weight1 =-29'd341584992;
//integer bais;
integer count;
always @(negedge clk or negedge rst)
begin
if(!rst) //reset,set values and bias
begin
// bais<=-50'd214979034352654;
// weight[0]<=29'd22506522;
// weight[1]<=-29'd341584992;
save<=0;
save1<=0;
save2<=0;
count<=0;
end
else
begin
save1<=x1*weight0;
save2<=x2*weight1;
count<=count+1;
if(count>518)
save<=save1+save2+bais;//Multiply the input by the weights and add up the results
end
end
assign data_out=save;
endmodule4.6 阈值判断模块
为什么需要阈值判断?
前面提到,我们在保存权值时将所有参数扩大了 1024 倍(即 $2^{10}$)。这意味着:
- 隐藏层的输出值也被放大了约1024倍;
- 输出层的计算结果同样被放大;
- 原本训练时,输出层ReLU后大于0即判定为目标信号;
- 现在由于数值被放大,不能简单以0为界,需要设定一个等效的阈值。
阈值推导:
- 原始判定边界:输出 > 0.5(二分类常用阈值);
- 放大后等效边界:
threshold = 0.5 × 1024 × 1024 = 524288; - 实际上,由于隐藏层和输出层都放大了1024倍,总放大倍数为 $1024^2 = 1048576$,因此:
threshold = 0.5 × 1048576 = 524288。
五、关键优化技巧
5.1 归一化的"逆向"处理
再次强调这个最重要的优化:
| 处理方式 | FPGA资源消耗 | 推荐度 |
|---|---|---|
| FPGA内做除法(adc/4095) | 极高,需除法器IP | ❌ |
| FPGA内用移位近似除法 | 中等,精度损失大 | △ |
| 权值预处理(本文方法) | 零额外消耗 | ✅ |
具体做法:
- 训练时归一化:$V_{\text{in}} = \frac{\text{adc_data}}{4095} \times 1.8$;
- 提取权值时:$w' = \frac{w}{1.8} \times 1024$,取整;
- FPGA输入:直接使用原始ADC值 $x$(0~4095);
- 等效运算。
5.2 权值二值化(BWN)降低功耗
为了进一步压缩模型、降低功耗,本文采用了 Binary-Weight-Networks (BWN) 对权值进行二值化:
- 权值只取
+1或-1; - 引入缩放因子 α 保证量化后的权值尽可能接近原始浮点权值。
二值化后,乘法运算退化为加减法或符号位判断,FPGA资源占用和动态功耗大幅下降。虽然精度略有损失,但在本场景中仍能保持97%以上的识别率。
二值化乘法器的Verilog实现:
// 二值化权值乘法:只需判断符号
wire signed [31:0] product;
assign product = bin_weight ? x_in : -x_in; // bin_weight=1为正,0为负相比32bit×32bit的硬件乘法器,这个逻辑几乎不消耗DSP资源。
5.3 亚稳态消除
ADC数据输入和CS信号是异步信号(相对于16MHz系统时钟),必须做双级触发器同步,防止亚稳态传播:
reg adc_dat_d1, adc_dat_d2;
always @(posedge clk) begin
adc_dat_d1 <= adc_dat; // 第一级
adc_dat_d2 <= adc_dat_d1; // 第二级(稳定后使用)
end
wire adc_dat_sync = adc_dat_d2;六、实验结果:功耗与性能
通过 Actel Libero SOC 对FPGA各模块进行功耗仿真,结果如下:
| 工作模式 | 静态功耗 | 动态功耗 | 总功耗 |
|---|---|---|---|
| 空闲态 | 0.046 mW | 0 | 0.046 mW |
| 正常工作 | 0.046 mW | 2.79 mW | 2.835 mW |
| Flash*Freeze模式 | 0.115 mW | 0 | 0.115 mW |
2.835 mW 的总功耗,对于低功耗设备来说是完全可以接受的。
各子模块动态功耗分解:
- 串并转换:0.01 mW
- 隐藏层神经元(含FIFO):0.083 mW
- ReLU激活:0.067 mW
- 输出层神经元:0.067 mW
七、总结与思考
本文介绍了一套完整的从算法到硬件的轻量化神经网络部署方案:
- 算法层:针对低功耗约束,设计了512-2-1极简BP网络,避免使用CNN/RNN等重算力模型;
- 数据层:在真实环境中采集目标信号,建立含噪样本库,并通过数据增强提升泛化能力;
- 硬件层:基于Actel低功耗FPGA,用FIFO流水、整数运算、ReLU硬判决、权值二值化等手段,实现了毫瓦级推理;
- 系统层:与射频前端(LNA、包络检波、ADC)紧密配合,形成完整的标签信号检测链路。
可扩展性:该FPGA神经网络框架不仅可用于目标信号检测,只需更换训练数据集和权值参数,即可用于FMCW、RFID等其他射频信号的识别,具备良好的通用性。




