Verilog实现对采样信号有效值(RMS)的计算

该文章已生成可运行项目,

有效值(RMS)的计算原理

有效值的数学定义是:对于连续信号,有效值是信号瞬时值的平方在一个周期内的平均值的平方根;
X r m s = 1 T ∫ 0 T [ x ( t ) ] 2 d t X_{rms} = \sqrt{\frac{1}{T} \int_{0}^{T} [x(t)]^2 dt} Xrms=T10T[x(t)]2dt

对于离散采样信号,计算公式为:
X r m s = 1 N ∑ i = 0 N − 1 x i 2 X_{rms} = \sqrt{\frac{1}{N} \sum_{i=0}^{N-1} x_i^2} Xrms=N1i=0N1xi2
其中:

  • N N N 是一个周期内的采样点数
  • x i x_i xi 是第 i i i 个采样点的数值

在FPGA/Verilog实现中,通常分三步:

  1. 对采样值进行平方运算
  2. 对平方结果进行累加并求平均
  3. 对平均值进行开方运算(牛顿迭代法

Verilog实现代码

以下是一个可综合的有效值计算模块,包含平方、累加平均、开方核心逻辑,适合FPGA实现:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2025/08/04 10:21:00
// Design Name: 
// Module Name: current_rms_calculator
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 电流有效值计算器,支持任意大小的采样窗口
// 
// Dependencies: advanced_divider.v, sqrt_calculator.v
// 
// Revision:
// Revision 0.01 - File Created
// Revision 0.02 - 增加窗口大小可调功能
// Revision 0.03 - 支持任意窗口大小,不再局限于2的幂次
// Revision 0.04 - 补充完整开方计算模块
// Revision 0.05 - 替换为高级除法器模块,增加错误处理
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module current_rms_calculator (
    input wire clk_100MHZ,                   // 系统时钟
    input wire rst_n,                 // 异步复位(高有效)
    input [15:0] data,  // 电流采样值(有符号16位)
    input sample_valid,          // 采样有效信号
    input [15:0] sin_cycle,    // 采样窗口大小(可调,任意正整数)
    output [31:0] rms_value,      // 计算得到的RMS值(无符号16位)
    output reg rms_valid            // RMS值有效信号

);

// 参数定义
// parameter MAX_WINDOW_SIZE = 1024;    // 最大窗口大小
// parameter SQUARE_WIDTH = 32;         // 平方结果的位宽
// parameter RMS_WIDTH = 16;            // RMS结果位宽




// 步骤1:对输入数据进行平方运算(有符号数处理)
reg signed [31:0] din_square;
reg signed [15:0] din_signed;

always @(posedge clk_100MHZ or negedge rst_n) begin
    if(!rst_n) begin
        din_signed <= 16'd0;
        din_square <= 32'd0;
    end else if(sample_valid) begin
        din_signed <= data;
        // 有符号数平方:先取绝对值再相乘,避免负数平方溢出
        din_square <= (din_signed >= 0) ? (din_signed * din_signed) : ((-din_signed) * (-din_signed));
    end
end

wire [31:0] avg_out;
wire avg_valid;

// // 步骤2:累加平方值并求平均(N为2的幂次,用移位代替除法)
sliding_window_avg_variable #(
    .DATA_WIDTH    (32),    // 重定义数据位宽为16
    .MAX_WINDOW    (200),  // 重定义最大窗口为2048
    .SUM_WIDTH     (40)     // 总和位宽:16 + log2(2048)=16+11=27,这里设41留余量
) u_sliding_window_avg_variable (
    .clk            (clk_100MHZ),          // 时钟信号
    .rst_n          (rst_n),        // 复位信号
    .din            (din_square),          // 输入数据
    .din_valid      (sample_valid),    // 输入有效
    .window_size    (200),  // 窗口大小配置
    .avg_out        (avg_out),      // 平均值输出
    .avg_valid      (avg_valid)     // 输出有效
);


// // 步骤3:开方运算(牛顿迭代法,适合硬件实现) 10个时钟周期才能完成开方运算
//核心公式:x_{n+1} = 1/2*(x_n + S/x_n)
reg [31:0] sqrt_result;
reg [31:0] sqrt_temp;
reg [4:0] sqrt_state; // 迭代状态机
reg [31:0] rms_out;

always @(posedge clk_100MHZ or negedge rst_n) begin
    if(!rst_n) begin
        sqrt_result <= 'd5000;
        sqrt_temp <= 'd5000;
        sqrt_state <= 'd0;
        rms_out <= 'd0;
    end 
    else begin//一直进行开方运算 10个时钟周期输出一次有效值
        case(sqrt_state)
            0: begin
                sqrt_temp <= avg_out;
                sqrt_result <= (avg_out/2);
                sqrt_state <= sqrt_state + 1'b1;
            end
            1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30: begin // 8次迭代(足够收敛)
                sqrt_result <= (sqrt_result + sqrt_temp / sqrt_result) >> 1;
                sqrt_state <= sqrt_state + 1'b1;
            end
            31: begin
                rms_out <= sqrt_result; // 迭代完成,输出有效值
                sqrt_state <= 'd0;
            end
        endcase
    end
end

endmodule

滑动窗口求取开方后的累加平均值

module sliding_window_avg_variable #(
    parameter DATA_WIDTH    = 32,        // 输入数据位宽
    parameter MAX_WINDOW    = 1024,      // 支持的最大窗口大小(可根据需求调整)
    parameter SUM_WIDTH     = 40         // 总和位宽(32 + log2(MAX_WINDOW) ≈ 40)
)(
    input  wire                     clk,            // 时钟信号
    input  wire                     rst_n,          // 低电平复位
    input  wire [DATA_WIDTH-1:0]    din,            // 输入32位数据
    input  wire                     din_valid,      // 输入数据有效信号
    input  wire [9:0]               window_size,    // 运行时配置的窗口大小(0~1023,需≥1)
    output reg  [DATA_WIDTH-1:0]    avg_out,        // 输出平均值(32位)
    output reg                      avg_valid       // 输出平均值有效信号
);

// 1. 定义滑动窗口寄存器(最大深度MAX_WINDOW)
reg [DATA_WIDTH-1:0] window_reg [0:MAX_WINDOW-1];
// 2. 辅助寄存器:记录已存入的数据个数、移出的旧数据、当前有效总和
reg [9:0]            data_cnt;       // 已存入数据计数(0~MAX_WINDOW)
reg [DATA_WIDTH-1:0] old_data;
reg [SUM_WIDTH-1:0]  sum_reg;

// 3. 滑动窗口移位+数据计数逻辑
integer i;
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        // 复位清零
        for(i = 0; i < MAX_WINDOW; i = i + 1) begin
            window_reg[i] <= {DATA_WIDTH{1'b0}};
        end
        old_data <= {DATA_WIDTH{1'b0}};
        data_cnt <= 10'd0;
    end 
    else if(din_valid) begin
        // 步骤1:更新数据计数(不超过当前窗口大小)
        if(data_cnt < window_size) begin
            data_cnt <= data_cnt + 10'd1;
        end

        // 步骤2:获取即将移出的旧数据(仅当窗口填满时)
        if(data_cnt >= window_size && window_size > 10'd0) begin
            old_data <= window_reg[window_size-1]; // 移出当前窗口的最旧数据
        end else begin
            old_data <= {DATA_WIDTH{1'b0}}; // 窗口未填满时,旧数据为0(仅累加)
        end

        // 步骤3:窗口数据移位(仅移位当前窗口大小内的数据)
        if(window_size > 10'd1) begin
            // 窗口填满后,才移位
            for(i = window_size-1; i > 0; i = i - 1) begin
                window_reg[i] <= window_reg[i-1];
            end
        end
        // 新数据始终存入第一个位置
        window_reg[0] <= din;
    end
end

// 4. 动态增量求和逻辑(适配窗口大小变化)
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        sum_reg <= {SUM_WIDTH{1'b0}};
    end else if(din_valid) begin
        if(data_cnt < window_size) begin
            // 窗口未填满:仅累加新数据
            sum_reg <= sum_reg + din;
        end else begin
            // 窗口已填满:增量更新(加新数据-减旧数据)
            sum_reg <= sum_reg + din - old_data;
        end
    end
end

// 5. 动态求平均值(通用除法逻辑,适配任意窗口大小)
// 方案:使用移位近似法(兼顾资源和精度),也可替换为除法器IP
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        avg_out <= {DATA_WIDTH{1'b0}};
        avg_valid <= 1'b0;
    end else if(din_valid && data_cnt >= window_size && window_size > 10'd0) begin
        // 仅当窗口填满且配置有效时,输出平均值
        // 通用除法实现:sum_reg / window_size
        // 方法1:直接用除法(综合器会自动优化,适合FPGA/ASIC)
        avg_out <= sum_reg / window_size;
        // 方法2:移位近似(可选,资源更少,精度略低)
        // avg_out <= (sum_reg << 16) / (window_size << 16); // 扩展位宽后除法,减少截断误差
        avg_valid <= 1'b1;
    end else begin
        avg_valid <= 1'b0;
    end
end

endmodule

testbench

`timescale 1ns / 1ps

module rms_sine_100_000points_fixed_v2();

    // 信号定义
    reg clk_100MHZ;
    reg rst_n;

    reg [15:0] sin_cycle;
    reg [15:0] sin_source;
    wire [31:0] rms_value;
    wire rms_valid;
    wire div_error;
    // 计数器、状态变量和计算用变量(全部在块外声明)
    reg [31:0] count;         // 
    reg [15:0] count_5us;
    reg sample_valid;
    /*
    real是 Verilog 中的实数数据类型,用于存储带小数的数值(类似 C 语言的double),取值范围远大于整数类型,精度约为双精度浮点数。
    Verilog 中real类型主要用于仿真阶段的数值计算(比如三角函数、浮点运算),不可综合(无法映射到硬件电路)
    */             
    real angle;              // 角度变量(移到块外声明)
    real sin_value;          // 正弦值变量(移到块外声明)
    real temp_paramter;
    
    // 正弦波参数
    parameter PI = 3.1415926535;
    parameter AMPLITUDE = 100;//32767;  // 16位有符号数最大值,正弦峰值,理论有效值23169
    parameter POINTS = 100_000;     // 一个周期的点数 100MHZ的时钟最大数据点数就是100K 最小步长



    // 生成100MHz时钟
    initial begin
        clk_100MHZ = 0;
        forever #5 clk_100MHZ = ~clk_100MHZ;
    end

    // 主测试流程
    initial begin
        // 初始化
        rst_n = 0;
        temp_paramter = 4;
        // 复位操作
        #100;
        rst_n = 1;

        #1000_000;
        temp_paramter = 2;
        #1000_000;
        temp_paramter = 3;

        #1000_000;
        temp_paramter = 4;

        #1000_000;
        temp_paramter = 5;
        #1000_000;
        temp_paramter = 9;  
                #1000_000;
        temp_paramter = 12;  
                #1000_000;
        temp_paramter = 7;  
                #1000_000;
        temp_paramter = 6;        
    end

//正弦波信号源
// 使用always块和计数器生成采样点
always @(posedge clk_100MHZ or negedge rst_n) begin
    if (~rst_n) begin
        count <= 32'd0;
        sin_source <= 16'd0;
        angle <= 0.0;
        sin_value <= 0.0;
    end 
    else begin
        if (count < POINTS) begin
            // 计算当前角度(弧度)和正弦值
            angle <= 2 * PI * count / POINTS;
            sin_value <= temp_paramter * AMPLITUDE * $sin(angle);
            sin_source <= $rtoi(sin_value);  // 量化为整数
            count <= count + 32'd1;
        end 
        else begin
            count <= 32'd0;                
        end
    end 
end


    //实例化采样
    //假设有AD采样
    always @(posedge clk_100MHZ or negedge rst_n) begin
        if (~rst_n) begin
            count_5us <= 10'd0;
            sample_valid <= 1'b0;
        end 
        else begin
            if (count_5us < 499) begin
                count_5us <= count_5us + 32'd1;
                sample_valid <= 1'b0;
            end 
            else begin
                count_5us <= 32'd0;
                sample_valid <= 1'b1;                
           end
        end 
    end


    // 实例化有效值计算模块
    current_rms_calculator uut (
        .clk_100MHZ(clk_100MHZ),
        .rst_n(rst_n),
        .data(sin_source),
        .sample_valid(sample_valid),
        .sin_cycle(sin_cycle),//周期
        .rms_value(rms_value)

    );


endmodule

代码关键部分解释

  1. 有符号数平方处理

    • 先将输入的有符号采样值取绝对值,再进行平方运算,避免负数平方导致的溢出问题。
    • 平方后的位宽扩展为 2*DATA_WIDTH,保证数据不丢失。
  2. 累加与平均

    • 累加器位宽设计为 2*DATA_WIDTH + $clog2(N),避免累加N个平方值时溢出。
    • 利用“移位代替除法”(仅当N为2的幂次时适用),提升硬件运算效率。
  3. 开方运算

    • 采用牛顿迭代法实现开方,这是硬件中最常用的开方算法,收敛速度快、资源占用少。
    • 通过8次迭代即可达到足够的精度(可根据实际需求调整迭代次数)。

模块使用说明

  1. 参数配置

    • DATA_WIDTH:根据你的采样精度设置(如12/16位)。
    • N:设置为一个周期内的采样点数,建议为2的幂次(如256、512、1024),简化除法运算。
  2. 输入输出

    • 输入:clk(系统时钟)、rst_n(复位)、din(采样数据)、din_valid(数据有效标志)。
    • 输出:rms_out(计算得到的有效值)、rms_valid(输出有效标志)。
  3. 前置条件

    • 确保采样时钟与系统时钟同步,采样率满足奈奎斯特采样定理。
    • 采样点数N需覆盖完整的信号周期,否则计算结果会有误差。

总结

  1. Verilog实现有效值计算的核心步骤是:平方运算 → 累加平均 → 开方运算,其中累加平均优先用移位代替除法以提升硬件效率。
  2. 有符号采样值需先取绝对值再平方,避免溢出;开方采用牛顿迭代法,通过多次迭代保证精度。
  3. 采样点数N建议设为2的幂次,且需覆盖完整信号周期,才能得到准确的有效值结果。
本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值