Added K IIR lpf filter

This commit is contained in:
Joppe Blondel
2025-10-19 17:02:29 +02:00
parent b2858ac5ee
commit 771fa58769
4 changed files with 115 additions and 1 deletions

View File

@@ -23,6 +23,7 @@ files_verilog = rtl/toplevel/top_generic.v
rtl/core/sigmadelta_sampler.v rtl/core/sigmadelta_sampler.v
rtl/core/sigmadelta_rcmodel_q15.v rtl/core/sigmadelta_rcmodel_q15.v
rtl/core/mul_const.v rtl/core/mul_const.v
rtl/core/lpf_iir_q15_k.v
rtl/arch/spartan-6/lvds_comparator.v rtl/arch/spartan-6/lvds_comparator.v
rtl/arch/spartan-6/clk_gen.v rtl/arch/spartan-6/clk_gen.v
files_con = boards/mimas_v1/constraints.ucf files_con = boards/mimas_v1/constraints.ucf
@@ -50,6 +51,7 @@ files_verilog = sim/tb/tb_nco_q15.v
rtl/core/lvds_comparator.v rtl/core/lvds_comparator.v
rtl/core/sigmadelta_rcmodel_q15.v rtl/core/sigmadelta_rcmodel_q15.v
rtl/core/mul_const.v rtl/core/mul_const.v
rtl/core/lpf_iir_q15_k.v
sim/overrides/sigmadelta_sampler.v sim/overrides/sigmadelta_sampler.v
sim/overrides/clk_gen.v sim/overrides/clk_gen.v
files_other = rtl/util/conv.vh files_other = rtl/util/conv.vh

64
rtl/core/lpf_iir_q15_k.v Normal file
View File

@@ -0,0 +1,64 @@
`timescale 1ns/1ps
// =============================================================================
// Low-Pass IIR Filter (Q1.15)
// Simple first-order infinite impulse response filter, equivalent to an
// exponential moving average. Provides an adjustable smoothing factor based
// on parameter K.
//
// Implements:
// Y[n+1] = Y[n] + (X[n] - Y[n]) / 2^K
//
// This is a purely digital one-pole low-pass filter whose time constant
// approximates that of an analog RC filter, where alpha = 1 / 2^K.
//
// The larger K is, the slower the filter responds (stronger smoothing).
// The smaller K is, the faster it reacts to changes.
//
// parameters:
// -- K : filter shift factor (integer, 4..14 typical)
// cutoff frequency Fs / ( * 2^K)
// larger K lower cutoff
//
// inout:
// -- clk : input clock
// -- rst_n : active-low reset
// -- x_q15 : signed 16-bit Q1.15 input sample (e.g., 0..0x7FFF)
// -- y_q15 : signed 16-bit Q1.15 filtered output
//
// Notes:
// - The arithmetic right shift implements division by 2^K.
// - Internal arithmetic is Q1.15 fixed-point with saturation
// to [0, 0x7FFF] (for non-negative signals).
// - Useful for smoothing noisy ADC / sigma-delta data streams
// or modeling an RC envelope follower.
// =============================================================================
module lpf_iir_q15_k #(
parameter integer K = 10 // try 8..12; bigger = more smoothing
)(
input wire clk,
input wire rst_n,
input wire signed [15:0] x_q15, // Q1.15 input (e.g., 0..0x7FFF)
output reg signed [15:0] y_q15 // Q1.15 output
);
wire signed [15:0] e_q15 = x_q15 - y_q15;
wire signed [15:0] delta_q15 = e_q15 >>> K; // arithmetic shift
wire signed [15:0] y_next = y_q15 + delta_q15; // clamp to [0, 0x7FFF] (handy if your signal is non-negative)
function signed [15:0] clamp01;
input signed [15:0] v;
begin
if (v < 16'sd0)
clamp01 = 16'sd0;
else if (v > 16'sh7FFF)
clamp01 = 16'sh7FFF;
else
clamp01 = v;
end
endfunction
always @(posedge clk or negedge rst_n) begin
if (!rst_n) y_q15 <= 16'sd0;
else y_q15 <= clamp01(y_next);
end
endmodule

View File

@@ -0,0 +1,42 @@
import numpy as np
import matplotlib.pyplot as plt
def bode_mag_phase(Fs, K, n=4096):
"""Return frequency, magnitude (dB), and phase (deg) for 1-pole IIR."""
alpha = 2.0 ** (-K)
a = 1.0 - alpha
w = np.linspace(0, np.pi, n)
ejw = np.exp(-1j * w)
H = alpha / (1 - a * ejw)
f = (w / (2 * np.pi)) * Fs
mag_db = 20 * np.log10(np.abs(H) + 1e-20)
phase_deg = np.unwrap(np.angle(H)) * 180 / np.pi
return f, mag_db, phase_deg
if __name__ == "__main__":
Fs = 15e6
Ks = [10, 8, 6, 4]
fig, (ax_mag, ax_phase) = plt.subplots(2, 1, sharex=True, figsize=(8, 6))
for K in Ks:
f, mag_db, phase_deg = bode_mag_phase(Fs, K, n=32768)
ax_mag.semilogx(f, mag_db, label=f"K={K}")
ax_phase.semilogx(f, phase_deg, label=f"K={K}")
# ---- Styling ----
f_max = 40e3 # 40 kHz
ax_mag.set_xlim(10, f_max) # start at 10 Hz for nicer log scaling
ax_mag.set_ylim(-60, 1)
ax_mag.set_title("Ideal Bode Response vs K")
ax_mag.set_ylabel("Magnitude [dB]")
ax_mag.grid(True, which='both')
ax_mag.legend()
ax_phase.set_xlabel("Frequency [Hz]")
ax_phase.set_ylabel("Phase [deg]")
ax_phase.grid(True, which='both')
ax_phase.set_xlim(10, f_max)
plt.tight_layout()
plt.show()

View File

@@ -27,7 +27,6 @@ module tb_sigmadelta();
.o(sd_o) .o(sd_o)
); );
wire signed [15:0] sample_q15; wire signed [15:0] sample_q15;
sigmadelta_rcmodel_q15 rc_model( sigmadelta_rcmodel_q15 rc_model(
.clk(clk), .resetn(resetn), .clk(clk), .resetn(resetn),
@@ -35,4 +34,11 @@ module tb_sigmadelta();
.sample_q15(sample_q15) .sample_q15(sample_q15)
); );
wire signed [15:0] y_q15;
lpf_iir_q15_k #(8) lpf(
.clk(clk), .rst_n(resetn),
.x_q15(sample_q15),
.y_q15(y_q15)
);
endmodule endmodule