前言
我的另一篇博客:Verilog功能模块——取滑动平均值(使用寄存器组)
两者用不同的方式实现相同的功能,
- 使用FIFO占用较少寄存器资源,适用于取值N较大的场合。
- 使用寄存器组不需要额外的IP,更简单,但消耗寄存器资源较多,适用于取值N较小(一般不大于1024)的场合。
一. 模块功能与应用场景
模块功能:对输入信号取滑动平均值。
滑动平均值:又名移动平均值,在简单平均值的基础上,通过顺序逐期增加新数据、减去旧数据求算移动平均值,借以消除偶然变动因素。
参考百度百科:滑动平均法
应用场景:
- 对平均值会变化,但变化速度较慢的信号求平均值
- 数字滤波中去除信号的直流偏置
二. 模块框图与使用说明
参数N表示求N个点的平均值,参数DIN_WIDTH控制输入信号位宽。
注意:
- din与din_valid应对齐
- clk应就是din与din_valid产生的时钟,这是为了保证一个有效数据din_valid只持续一个时钟周期的高电平
- N应为2的n次方,如:128(27),256(28),512(29),1024(210)等
- 为使平均值计算正确,N至少应包含一个完整信号周期的点,一般要大于2~3个信号周期的点数
- 这里的ac_signal为din - moving_avg表示不带直流偏置的交流信号
- 此模块需要搭配一个首字直通的单时钟FIFO使用,此FIFO的时钟应与此模块时钟相同
- FIFO深度和位宽需要与模块参数N和DIN_WIDTH分别保持一致
模块封装为IP后的框图:
模块IP参数配置界面:
三. 模块代码
/*
* @Author : Xu Dakang
* @Email : XudaKang_up@qq.com
* @Date : 2021-04-14 16:14:46
* @LastEditors : Xu Dakang
* @LastEditTime : 2021-05-19 22:34:24
* @Filename : getMovingAvg.sv
* @Description : 求N个有符号数的滑动平均值
*/
/*
! 模块功能: 求N个有符号数的滑动平均值
* 思路:
1.求N个数的和,再除以N即为N个数的平均值
2.当第N+1个数到来后, 总和去除第1个数再加上第N+1个数, 再求平均值即为1~N+1个数的平均值, 以此类推
*/
module getMovingAvg
#(
parameter N = 1024, // 求N个数的平均值,N应等于2的n次方,如512,1024,2048等
parameter DIN_WIDTH = 24 // 输入数据的位宽
)(
output logic signed [DIN_WIDTH-1 : 0] moving_avg,
output logic signed [DIN_WIDTH-1 : 0] ac_signal,
output logic ac_signal_valid,
input logic signed [DIN_WIDTH-1 : 0] din,
input logic din_valid,
input logic signed [DIN_WIDTH-1 : 0] fwft_fifo_dout,
output logic fwft_fifo_rd_en,
input logic fwft_fifo_empty,
output logic signed [DIN_WIDTH-1 : 0] fwft_fifo_din,
output logic fwft_fifo_wr_en,
input logic fwft_fifo_full,
input logic clk,
input logic rstn
);
//< 信号同步 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
logic signed [DIN_WIDTH-1 : 0] din_r1;
logic signed [DIN_WIDTH-1 : 0] din_r2;
always_ff @(posedge clk) begin
din_r1 <= din;
din_r2 <= din_r1;
end
logic din_valid_r1;
logic din_valid_r2;
always_ff @(posedge clk) begin
din_valid_r1 <= din_valid;
din_valid_r2 <= din_valid_r1;
end
//< 信号同步 ------------------------------------------------------------
//> FWFT FIFO存储N个数 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
logic [$clog2(N) : 0] din_cnt;
always_ff @(posedge clk) begin // FIFO写入计数
if (~rstn)
din_cnt <= '0;
else if (din_valid_r2 && din_cnt[$clog2(N)] != 1)
din_cnt <= din_cnt + 1'b1;
else
din_cnt <= din_cnt;
end
// fwft FIFO输出
always_comb begin
fwft_fifo_din = din_r2;
fwft_fifo_wr_en = din_valid_r2;
fwft_fifo_rd_en = din_cnt[$clog2(N)] == 1 && din_valid_r2;
end
//> FWFT FIFO存储N个数 ------------------------------------------------------------
//< 求和 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
logic signed [$clog2(N)+DIN_WIDTH-1 : 0] sum;
always_ff @(posedge clk) begin
if (~rstn)
sum <= '0;
else if (din_valid_r2)
if (din_cnt[$clog2(N)] != 1)
sum <= din_r2 + sum; // 先减再加,没有位宽溢出的风险
else
sum <= (din_r2 - fwft_fifo_dout) + sum;
else
sum <= sum;
end
//< 求和 ------------------------------------------------------------
//> 输出平均值 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
always_ff @(posedge clk) begin
if (~rstn)
moving_avg <= '0;
else
moving_avg <= sum[$clog2(N)+DIN_WIDTH-1 : $clog2(N)]; // 取高数据位
end
//> 输出平均值 ------------------------------------------------------------
//< 求AC信号 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
always_ff @(posedge clk) begin
if (~rstn)
ac_signal <= '0;
else if (din_valid_r2)
ac_signal <= din_r2 - moving_avg;
else
ac_signal <= ac_signal;
end
always_ff @(posedge clk) begin
if (~rstn)
ac_signal_valid <= '0;
else
ac_signal_valid <= din_valid_r2;
end
//< 求AC信号 ------------------------------------------------------------
endmodule
四. testbench
/*
* @Author : Xu Dakang
* @Email : XudaKang_up@qq.com
* @Date : 2021-04-21 14:29:04
* @LastEditors : Xu Dakang
* @LastEditTime : 2021-05-20 00:32:15
* @Filename : getMovingAvg_tb.sv
* @Description : testbench of getMovingAvg
*/
module getMovingAvg_tb;
timeunit 1ns;
timeprecision 1ps;
localparam DIN_WIDTH = 24;
logic [DIN_WIDTH-1 : 0] ac_signal;
logic ac_signal_valid;
logic [DIN_WIDTH-1 : 0] din;
logic din_valid;
logic [DIN_WIDTH-1 : 0] moving_avg;
logic clk;
logic rstn;
getMovingAvg_system_wrapper getMovingAvg_system_wrapper_inst (.*);
// 生成时钟
localparam CLKT = 2;
initial begin
clk = 0;
forever #(CLKT / 2) clk = ~clk;
end
// 导入输入波形文件 Vivado只能识别绝对路径 注意修改!!!
string din_path = "F:/OneDrive/VivadoPrj/getMovingAvg_useFIFO/getMovingAvg_useFIFO.srcs/sim_1/new/sin+0.5.txt"; // 可选 sin sin+0.5 sin-0.5 sin+1.0 sin-1.0
localparam DATA_NUM = 10240; // 数据量, 也就是txt文件的行数, 如果此参数大于数据行数, 读取到的内容为不定态
logic [DIN_WIDTH-1 : 0] din_wave_data [DATA_NUM]; // 读取输入波形数据
initial begin
$readmemb(din_path, din_wave_data, 0, DATA_NUM-1); // vivado读取txt文件
end
int cnt;
initial begin
rstn = 0;
din_valid = 0;
#(CLKT * 10) rstn = 1;
for (int i = 0; i < DATA_NUM; i++) begin
din = din_wave_data[i];
cnt++;
din_valid = 1;
#(CLKT);
// din_valid = 0;
// #(CLKT * ({$random} % 12));
end
din_valid = 0;
#(CLKT * 10) $stop;
end
endmodule
五. 仿真验证
仿真工具:Vivado 2020.2 Simulator。
lock Design如下图,注意:
- 模块的参数N与FIFO深度保持一致
- 模块数据位宽与FIFO数据位宽保持一致
- FIFO必须是FWFT类型的
- 输入为(sinx + 0.5)信号:
- 输入为(sinx - 0.5)信号:
- 输入为(sinx + 1.0)信号:
- 输入为(sinx - 1.0)信号:
六. 工程分享
getMovingAvg_useFIFO 取滑动平均值模块(使用FIFO) vivado 2020.2工程.7z。
欢迎大家关注我的公众号:徐晓康的博客,回复以下代码获取。
1653
建议复制过去不会码错字!
徐晓康的博客持续分享高质量硬件、FPGA与嵌入式知识,软件,工具等内容,欢迎大家关注。