IIR Filter in Verilog: Biquad / Direct-Form with Code
An infinite-impulse-response (IIR) filter computes each output from the current and past inputs and from its own past outputs: y[n] = b0·x[n] + b1·x[n-1] + … − a1·y[n-1] − a2·y[n-2] − …. That feedback path is what makes the impulse response infinitely long, and it lets a tiny number of taps realise sharp, resonant responses that a FIR would need hundreds of taps to match.
This page gives you runnable, simulator-verified IIR filter Verilog code: a parameterized first-order section and a second-order biquad, both in direct-form I with signed fixed-point coefficients and a recursive delay line of past outputs. Each testbench self-checks every sample against a bit-exact golden difference equation, so a real bug would print a FAIL line.
A parameterized first-order IIR section. Coefficients are signed Q14 fixed-point; the accumulator sums the two feed-forward products and subtracts the single feedback product, then an arithmetic right shift (>>>) rescales back to the sample format. The testbench drives an impulse followed by a step and checks each output against a golden model that reuses the identical integer arithmetic.
// First-order IIR direct-form I, signed fixed-point.
// y[n] = ( b0*x[n] + b1*x[n-1] - a1*y[n-1] ) >>> SHIFT
module iir1 #(
parameter DW = 16, // sample width (signed)
parameter CW = 16, // coefficient width (signed)
parameter SHIFT = 14 // Q(CW-2) fixed-point scaling
)(
input clk,
input rst, // sync active-high
input signed [CW-1:0] b0,
input signed [CW-1:0] b1,
input signed [CW-1:0] a1,
input signed [DW-1:0] x,
output reg signed [DW-1:0] y
);
reg signed [DW-1:0] x_d1; // x[n-1]
// accumulator must hold product + sum headroom
reg signed [DW+CW:0] acc;
always @(posedge clk) begin
if (rst) begin
x_d1 <= 0;
y <= 0;
end else begin
acc = b0*x + b1*x_d1 - a1*y; // blocking for this-cycle combine
y <= acc >>> SHIFT; // arithmetic shift rescale
x_d1 <= x;
end
end
endmodule
module tb;
localparam DW = 16, CW = 16, SHIFT = 14;
reg clk = 0, rst;
reg signed [DW-1:0] x;
reg signed [CW-1:0] b0, b1, a1;
wire signed [DW-1:0] y;
iir1 #(.DW(DW), .CW(CW), .SHIFT(SHIFT)) dut
(.clk(clk), .rst(rst), .b0(b0), .b1(b1), .a1(a1), .x(x), .y(y));
always #5 clk = ~clk;
// bit-exact golden model mirroring the hardware arithmetic
reg signed [DW-1:0] g_y, g_xd1;
reg signed [DW+CW:0] g_acc;
integer errors, k;
reg signed [DW-1:0] stim [0:7];
task golden_step(input signed [DW-1:0] xin);
begin
g_acc = b0*xin + b1*g_xd1 - a1*g_y;
g_y = g_acc >>> SHIFT;
g_xd1 = xin;
end
endtask
initial begin
// Q14 coefficients: b0=0.5, b1=0.5, a1=-0.5 (a leaky integrator-ish LPF)
b0 = 16'sd8192; b1 = 16'sd8192; a1 = -16'sd8192;
errors = 0;
g_y = 0; g_xd1 = 0;
// impulse then step
stim[0]=100; stim[1]=0; stim[2]=0; stim[3]=0;
stim[4]=200; stim[5]=200; stim[6]=200; stim[7]=200;
// reset
rst = 1; x = 0; @(posedge clk); #1;
rst = 0;
for (k = 0; k < 8; k = k + 1) begin
x = stim[k];
golden_step(stim[k]); // reference for the value latched this edge
@(posedge clk); #1; // after the edge y holds this sample's output
if (y !== g_y) begin
$display("FAIL n=%0d x=%0d got y=%0d exp=%0d", k, stim[k], y, g_y);
errors = errors + 1;
end else begin
$display("PASS n=%0d x=%0d y=%0d", k, stim[k], y);
end
end
if (errors == 0) $display("ALL TESTS PASSED");
else $display("TOTAL FAILURES=%0d", errors);
$finish;
end
endmodule
PASS n=0 x=100 y=50 PASS n=1 x=0 y=75 PASS n=2 x=0 y=37 PASS n=3 x=0 y=18 PASS n=4 x=200 y=109 PASS n=5 x=200 y=254 PASS n=6 x=200 y=327 PASS n=7 x=200 y=363 ALL TESTS PASSEDThe log shows the impulse response decaying (
50, 75, 37, 18) and then the step response settling toward a steady state, with each sample matching the golden difference equation exactly. Because the hardware and the reference use the same integer multiply and arithmetic shift, an off-by-one in the feedback term or a logical (vs arithmetic) shift would immediately produce a FAIL.A second-order biquad in direct-form I — three feed-forward taps and two feedback taps — with wider Q15 coefficients. The two poles let it resonate, so the impulse response rings before decaying. The testbench sweeps an impulse and a short burst and checks all ten outputs against a bit-exact golden model.
// Biquad (second-order) IIR, direct-form I, signed fixed-point. // y[n] = ( b0*x[n]+b1*x[n-1]+b2*x[n-2] - a1*y[n-1] - a2*y[n-2] ) >>> SHIFT module biquad #( parameter DW = 16, parameter CW = 18, parameter SHIFT = 15 )( input clk, input rst, input signed [CW-1:0] b0, b1, b2, a1, a2, input signed [DW-1:0] x, output reg signed [DW-1:0] y ); reg signed [DW-1:0] x1, x2; // x[n-1], x[n-2] reg signed [DW-1:0] y1; // y[n-1] reg signed [DW+CW+1:0] acc; always @(posedge clk) begin if (rst) begin x1 <= 0; x2 <= 0; y1 <= 0; y <= 0; end else begin acc = b0*x + b1*x1 + b2*x2 - a1*y - a2*y1; y <= acc >>> SHIFT; y1 <= y; // old y[n-1] becomes y[n-2] x2 <= x1; x1 <= x; end end endmodule module tb; localparam DW = 16, CW = 18, SHIFT = 15; reg clk = 0, rst; reg signed [DW-1:0] x; reg signed [CW-1:0] b0,b1,b2,a1,a2; wire signed [DW-1:0] y; biquad #(.DW(DW), .CW(CW), .SHIFT(SHIFT)) dut (.clk(clk), .rst(rst), .b0(b0),.b1(b1),.b2(b2),.a1(a1),.a2(a2), .x(x), .y(y)); always #5 clk = ~clk; // bit-exact golden model (same integer arithmetic/rounding as RTL) reg signed [DW-1:0] g_y, g_y1, g_x1, g_x2; reg signed [DW+CW+1:0] g_acc; integer errors, k; reg signed [DW-1:0] stim [0:9]; task golden_step(input signed [DW-1:0] xin); reg signed [DW-1:0] new_y; begin g_acc = b0*xin + b1*g_x1 + b2*g_x2 - a1*g_y - a2*g_y1; new_y = g_acc >>> SHIFT; g_y1 = g_y; g_x2 = g_x1; g_x1 = xin; g_y = new_y; end endtask initial begin // Q15 coefficients (a damped resonator-style set) b0 = 18'sd3277; // 0.10 b1 = 18'sd6554; // 0.20 b2 = 18'sd3277; // 0.10 a1 = -18'sd45875; // -1.40 a2 = 18'sd19661; // 0.60 errors = 0; g_y=0; g_y1=0; g_x1=0; g_x2=0; stim[0]=1000; stim[1]=0; stim[2]=0; stim[3]=0; stim[4]=0; stim[5]=500; stim[6]=500; stim[7]=500; stim[8]=0; stim[9]=0; rst = 1; x = 0; @(posedge clk); #1; rst = 0; for (k = 0; k < 10; k = k + 1) begin x = stim[k]; golden_step(stim[k]); @(posedge clk); #1; if (y !== g_y) begin $display("FAIL n=%0d x=%0d got y=%0d exp=%0d", k, stim[k], y, g_y); errors = errors + 1; end else begin $display("PASS n=%0d x=%0d y=%0d", k, stim[k], y); end end if (errors == 0) $display("ALL TESTS PASSED"); else $display("TOTAL FAILURES=%0d", errors); $finish; end endmodulePASS n=0 x=1000 y=100 PASS n=1 x=0 y=340 PASS n=2 x=0 y=516 PASS n=3 x=0 y=518 PASS n=4 x=0 y=415 PASS n=5 x=500 y=320 PASS n=6 x=500 y=349 PASS n=7 x=500 y=496 PASS n=8 x=0 y=635 PASS n=9 x=0 y=641 ALL TESTS PASSEDThe impulse (
x=1000at n=0) produces a ringing, decaying response — the signature of a two-pole IIR — and the later burst excites it again. All ten samples match the golden model bit-for-bit, which confirms the feedback ordering is right: the RTL multipliesa1by the registeredy(that is y[n-1]) anda2byy1(y[n-2]), exactly as the golden step does.How the direct-form I IIR works
A direct-form I section keeps two separate delay lines: one for past inputs (the feed-forward or zeros path, coefficients
b) and one for past outputs (the feedback or poles path, coefficientsa). Each clock edge the design forms one multiply-accumulate:acc = b0*x[n] + b1*x[n-1] + b2*x[n-2] - a1*y[n-1] - a2*y[n-2] y[n] = acc >>> SHIFTThe subtraction on the feedback terms follows the standard transfer function
H(z) = (b0 + b1 z⁻¹ + b2 z⁻²) / (1 + a1 z⁻¹ + a2 z⁻²). Becausea0is normalised to 1, there is no divide in hardware — the recursion simply reuses the previously computed outputs.Why fixed-point and the shift
Coefficients like 0.5 or -1.4 cannot be stored as integers, so they are scaled by 2SHIFT (Q-format). A coefficient times a sample therefore lands SHIFT bits too high, and the arithmetic right shift
>>>brings the result back to the sample's scale while preserving sign. Using>>>on asignedaccumulator is essential: a logical shift>>would corrupt negative outputs.FIR vs IIR: feedback and stability
A FIR filter has no feedback — its output depends only on a finite window of inputs, so it is always stable and can be made exactly linear-phase. An IIR filter feeds its own outputs back, which is why it can hit a sharp magnitude response with very few taps, but it introduces two hazards that FIRs never have:
- Stability. The poles (roots of the denominator) must lie strictly inside the unit circle. A coefficient typo or an over-aggressive design can push a pole outside, and the output grows without bound.
- Limit cycles. Even a stable IIR can get stuck in small self-sustaining oscillations once quantisation is applied in the feedback loop, because a rounded output keeps re-exciting the recursion.
Neither of these can happen in a pure FIR. That trade — efficiency for the need to manage stability — is the heart of every IIR interview question.
Direct-form I vs direct-form II
Direct-form I (used above) stores delayed inputs and delayed outputs separately — for a biquad that is four delay registers. Direct-form II merges them into a single delay line on an intermediate state
w[n], halving the number of delay elements to two for a biquad.The catch is internal range. Direct-form II computes
w[n] = x[n] - a1 w[n-1] - a2 w[n-2]first, and that intermediate node can swing much larger than the input or output, so it needs more guard bits to avoid overflow. In fixed-point hardware, direct-form I (or the transposed direct-form II) is usually preferred precisely because its internal signals stay closer to the input/output range and are easier to scale. For high-order filters you almost always cascade biquads rather than build one monolithic high-order section, because the poles of a long direct-form filter become numerically fragile.Coefficient scaling, overflow, and the feedback quantiser
Choosing
SHIFTis a balance. More fractional bits means finer coefficient resolution and a pole placed closer to where you intended — important because near the unit circle a tiny coefficient error moves the pole a lot and can change the filter from stable to unstable. Fewer fractional bits leaves more integer headroom against overflow.
- Accumulator width. Size
accas sample-width + coefficient-width + log2(number of taps) so the sum of products never wraps before the shift. The examples useDW+CW(plus a bit) for exactly this reason.- Output saturation. Real IIR hardware usually saturates
yinstead of letting it wrap, because a wrap in the feedback path can be catastrophic — it feeds a wildly wrong value back into the recursion.- Feedback quantisation. The shift that rescales
y[n]is a quantiser inside the loop. That rounding is what causes limit cycles and is why the golden model must round identically — see the next section.Common mistakes and why the golden model must be bit-exact
- Logical shift on signed data. Writing
acc >> SHIFTinstead of>>>breaks every negative output. Declare the accumulatorsignedand use>>>.- Wrong sign on the feedback taps. The denominator is
1 + a1 z⁻¹ + …, so the hardware subtractsa1*y[n-1]. Flipping that sign silently changes the pole locations.- Stale vs updated state. In direct-form I you must multiply by the old y[n-1]/y[n-2], not the value being computed this cycle. The non-blocking updates (
y1 <= y;) handle this; the testbench golden step reproduces the same ordering.- Checking against floating point. If your reference model uses real-number math and rounds differently from the RTL shift, the two drift apart after a few samples even when the hardware is correct. The only trustworthy reference for a fixed-point IIR is a bit-exact integer model that performs the same multiply and the same arithmetic shift — which is exactly why both testbenches above recompute the difference equation with the identical operators.
Synthesis notes
Each example maps to one DSP/MAC block per multiply plus an adder tree, with the delay line as flip-flops. At one multiply-accumulate per clock the whole biquad can run in a single cycle, but the feedback multiply is in the critical path, so for high clock targets you pipeline the feed-forward sum and register the product terms — being careful that the extra latency does not break the single-sample feedback delay. Keep coefficients as parameters or input ports (as shown) so the same RTL serves many filter shapes, and add saturation logic on
ybefore trusting it in a product. Verify the final quantised coefficients still place the poles inside the unit circle after rounding toCWbits.FAQ
Can I make this a general Nth-order IIR? Yes, but cascade biquads instead of one long section — the poles of a high-order direct form are too sensitive to coefficient quantisation.
Why no divide? The transfer function is normalised so
a0 = 1; the recursion only needs the previously computed outputs, never a division.How do I stop limit cycles? Add a small amount of error feedback / proper rounding, use enough fractional bits, or saturate — and always simulate with the bit-exact model to catch them.
Have questions about this topic?
Share your question in comments or talk to our mentor team for batch guidance.▶ Try it in the ChipXpert LabRun it in the LabOpen this design in the free in-browser simulator — edit, compile and see the waveform/log instantly.
Learn Verilog with mentors, labs and placement support
This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.
Explore VLSI Courses Hi! Ask me about courses, fees, batches or discounts. ×ChipXpert AssistantAsk about courses, fees, batches and centres