Verilog功能模块——取滑动平均值

一. 模块功能与应用场景

模块功能:对输入信号取滑动平均值。

滑动平均值:又名移动平均值,在简单平均值的基础上,通过顺序逐期增加新数据、减去旧数据求算移动平均值,借以消除偶然变动因素。

参考百度百科:滑动平均法

应用场景:

  • 对平均值会变化,但变化速度较慢的信号求平均值
  • 数字滤波中去除信号的直流偏置

二. 模块框图与使用说明

参数N表示求N个点的平均值,参数DIN_WIDTH控制输入信号位宽。

注意:

  1. din与din_valid应对齐
  2. clk应就是din与din_valid产生的时钟,这是为了保证一个有效数据din_valid只持续一个时钟周期的高电平
  3. N应为2的n次方,如:128(27),256(28),512(29),1024(210)等
  4. 为使平均值计算正确,N至少应包含一个完整信号周期的点,一般要大于2~3个信号周期的点数,N也不易过大,否则会消耗较多FPGA资源

三. 模块代码

/*
 * @Author       : Xu Dakang
 * @Email        : [email protected]
 * @Date         : 2021-04-14 16:14:46
 * @LastEditors  : Xu Dakang
 * @LastEditTime : 2021-05-06 21:32:36
 * @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,

  input  logic signed [DIN_WIDTH-1 : 0] din,
  input  logic                          din_valid,

  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;
logic din_valid_r3;
always_ff @(posedge clk) begin
  din_valid_r1 <= din_valid;
  din_valid_r2 <= din_valid_r1;
  din_valid_r3 <= din_valid_r2;
end
//< 信号同步 ------------------------------------------------------------



//> 存储N个数 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
logic [$clog2(N)-1 : 0] din_cnt;
always_ff @(posedge clk, negedge rstn) begin
  if (~rstn)
    din_cnt <= '0;
  else if (din_valid_r2)
    din_cnt <= din_cnt + 1'b1;
  else
    din_cnt <= din_cnt;
end


logic signed [DIN_WIDTH-1 : 0] din_array [N];
always_ff @(posedge clk, negedge rstn) begin // 存储输入数据
  if (~rstn)
    din_array <= '{default : '0};
  else if (din_valid_r2)
    din_array[din_cnt] <= din_r2;
  else
    din_array <= din_array;
end
//> 存储N个数 ------------------------------------------------------------



//< 求和 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
logic signed [$clog2(N)+DIN_WIDTH-1 : 0] sum;
always_ff @(posedge clk, negedge rstn) begin
  if (~rstn)
    sum <= '0;
  else if (din_valid_r2)
    sum <= sum - din_array[din_cnt] + din_r2; // 先减再加,没有位宽溢出的风险
  else
    sum <= sum;
end
//< 求和 ------------------------------------------------------------



//> 输出平均值 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
always_ff @(posedge clk, negedge rstn) begin
  if (~rstn)
    moving_avg <= '0;
  else if (din_valid_r3)
    moving_avg <= sum[$clog2(N)+DIN_WIDTH-1 : $clog2(N)]; // 取高数据位
  else
    moving_avg <= moving_avg;
end
//> 输出平均值 ------------------------------------------------------------



endmodule

四. testbench

/*
 * @Author       : Xu Dakang
 * @Email        : [email protected]
 * @Date         : 2021-04-21 14:29:04
 * @LastEditors  : Xu Dakang
 * @LastEditTime : 2021-05-06 20:54:14
 * @Filename     : getMovingAvg_tb.sv
 * @Description  : testbench of getMovingAvg
*/



module getMovingAvg_tb;

timeunit 1ns;
timeprecision 1ps;

localparam  N = 1024;
localparam  DIN_WIDTH = 24;

logic signed [DIN_WIDTH-1 : 0] moving_avg;

logic signed [DIN_WIDTH-1 : 0] din;
logic din_valid;

logic clk;
logic rstn;

getMovingAvg #(
  .N         (N),
  .DIN_WIDTH (DIN_WIDTH)
) getMovingAvg_inst (.*);



// 生成时钟
localparam CLKT = 2;
initial begin
  clk = 0;
  forever #(CLKT / 2) clk = ~clk;
end



// 导入输入波形文件,无法使用相对路径,注意修改!!!
string din_path = "F:/OneDrive/VivadoPrj/getMovingAvg/getMovingAvg.srcs/sim_1/new/sin-1.0.txt"; // 可选 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



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];
    din_valid = 1;
    #(CLKT);
    din_valid = 0;
    #(CLKT * ({
     $random} % 5));
  end
  din_valid = 0;

  #(CLKT * 10) $stop;
end



endmodule

五. 仿真验证

仿真工具:Vivado 2020.2 Simulator。

  1. 输入为(sinx + 0.5)信号:

    Verilog功能模块——取滑动平均值_第1张图片
  2. 输入为(sinx - 0.5)信号:

    Verilog功能模块——取滑动平均值_第2张图片
  3. 输入为(sinx + 1.0)信号:

    Verilog功能模块——取滑动平均值_第3张图片
  4. 输入为(sinx - 1.0)信号:

    Verilog功能模块——取滑动平均值_第4张图片

六. 工程分享

getMovingAvg 取滑动平均值模块 vivado 2020.2工程.7z

链接:https://pan.baidu.com/s/1YL0q2i2UfWdlCGSN7GOgnQ
提取码:uxxn

你可能感兴趣的:(Verilog,verilog,systemverilog,功能模块,滑动平均值)