Added K IIR lpf filter
This commit is contained in:
@@ -23,6 +23,7 @@ files_verilog = rtl/toplevel/top_generic.v
|
||||
rtl/core/sigmadelta_sampler.v
|
||||
rtl/core/sigmadelta_rcmodel_q15.v
|
||||
rtl/core/mul_const.v
|
||||
rtl/core/lpf_iir_q15_k.v
|
||||
rtl/arch/spartan-6/lvds_comparator.v
|
||||
rtl/arch/spartan-6/clk_gen.v
|
||||
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/sigmadelta_rcmodel_q15.v
|
||||
rtl/core/mul_const.v
|
||||
rtl/core/lpf_iir_q15_k.v
|
||||
sim/overrides/sigmadelta_sampler.v
|
||||
sim/overrides/clk_gen.v
|
||||
files_other = rtl/util/conv.vh
|
||||
|
||||
64
rtl/core/lpf_iir_q15_k.v
Normal file
64
rtl/core/lpf_iir_q15_k.v
Normal 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π * 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
|
||||
42
scripts/filter_design/bode_compare_Ks_lpf_iir_q15_k.py
Normal file
42
scripts/filter_design/bode_compare_Ks_lpf_iir_q15_k.py
Normal 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()
|
||||
@@ -27,7 +27,6 @@ module tb_sigmadelta();
|
||||
.o(sd_o)
|
||||
);
|
||||
|
||||
|
||||
wire signed [15:0] sample_q15;
|
||||
sigmadelta_rcmodel_q15 rc_model(
|
||||
.clk(clk), .resetn(resetn),
|
||||
@@ -35,4 +34,11 @@ module tb_sigmadelta();
|
||||
.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
|
||||
|
||||
Reference in New Issue
Block a user