FA-91791 / Digital signal filters / Open access
Q15 FIR shifts each product before accumulating · case 01
Many small products each truncate to zero or -1, biasing the output.
ROOT CAUSE
Every product is shifted down to Q15 before the sum, discarding the guard bits.
VERIFIED REPAIR
Accumulate full-precision products and round once at the end.
Unsuccessful approach: The attempted repair rounds each product before summing, which still accumulates rounding error.
Case contract
Input [coeffs, samples] as Q15 integers. Each output accumulates the full-precision products c_k x[n-k] (zero history) in a wide accumulator, then rounds once (add 2**14, arithmetic shift 15) and saturates to int16. Return {"y": outputs, "saturations": number of clipped outputs}.
Why this case matters
DSP MAC units keep guard bits and round once; per-product rounding or missing overflow accounting degrades fixed-point FIRs.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
coefs, xs = x
hist = [0] * len(coefs)
out = []
sat = 0
for v in xs:
hist = [v] + hist[:-1]
acc = sum((c * s) >> 15 for c, s in zip(coefs, hist))
y = acc
if y > 32767:
y = 32767
sat += 1
elif y < -32768:
y = -32768
sat += 1
out.append(y)
return {'y': out, 'saturations': sat}
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: exact positive rail', [[16384, 16384], [32767, 32767, 0]], {'y': [16384, 32767, 16384], 'saturations': 0}], ['regression: exact rail then negative overflow', [[16384, 16384, 32767], [32767, 32767, -32768, -32768]], {'y': [16384, 32767, 32766, -2], 'saturations': 0}], ['regression: random q15 fir 0', [[-32619, -22239, 2111], [244, -26104, -17179, -16]], {'y': [-243, 25820, 32767, 9993], 'saturations': 1}], ['control: small products lost', [[1000, 1000, 1000], [100, 100, 100, 100]], {'y': [3, 6, 9, 9], 'saturations': 0}], ['control: negative overflow', [[32767, 32767], [-32768, -32768]], {'y': [-32767, -32768], 'saturations': 1}], ['control: positive overflow', [[32767, 32767], [32767, 32767]], {'y': [32766, 32767], 'saturations': 1}], ['control: asymmetric taps', [[16384, 8192, 0, -4096], [1000, 0, 0, 0, 0]], {'y': [500, 250, 0, -125, 0], 'saturations': 0}]], [['regression: random q15 fir 1', [[-2422, 8115, -26238], [81, -19840, 164, 18, -31318, 26443]], {'y': [-6, 1487, -4990, 15926, 2188, -9725], 'saturations': 0}], ['regression: random q15 fir 2', [[-1074, -846, 29231], [-7102, 287, 18053, 24830, -286, 148]], {'y': [233, 174, -6935, -1024, 15473, 22152], 'saturations': 0}], ['regression: random q15 fir 0', [[-32619, -22239, 2111], [244, -26104, -17179, -16]], {'y': [-243, 25820, 32767, 9993], 'saturations': 1}], ['control: random q15 fir 14', [[2191], [5955, -128]], {'y': [398, -9], 'saturations': 0}], ['control: random q15 fir 16', [[-1447], [-227, 108]], {'y': [10, -5], 'saturations': 0}], ['control: random q15 fir 30', [[-1704], [264, 265, -20482, -11272]], {'y': [-14, -14, 1065, 586], 'saturations': 0}], ['control: small products lost', [[1000, 1000, 1000], [100, 100, 100, 100]], {'y': [3, 6, 9, 9], 'saturations': 0}]], [['regression: random q15 fir 4', [[375, 13267], [120, 1419, -3648]], {'y': [1, 65, 533], 'saturations': 0}], ['regression: random q15 fir 5', [[21007, -2568, -1633], [-267, 32265, 7287, 49]], {'y': [-171, 20705, 2156, -2148], 'saturations': 0}], ['regression: random q15 fir 2', [[-1074, -846, 29231], [-7102, 287, 18053, 24830, -286, 148]], {'y': [233, 174, -6935, -1024, 15473, 22152], 'saturations': 0}], ['control: negative overflow', [[32767, 32767], [-32768, -32768]], {'y': [-32767, -32768], 'saturations': 1}], ['control: positive overflow', [[32767, 32767], [32767, 32767]], {'y': [32766, 32767], 'saturations': 1}], ['control: asymmetric taps', [[16384, 8192, 0, -4096], [1000, 0, 0, 0, 0]], {'y': [500, 250, 0, -125, 0], 'saturations': 0}], ['control: random q15 fir 14', [[2191], [5955, -128]], {'y': [398, -9], 'saturations': 0}]], [['regression: random q15 fir 7', [[-2335, 2343], [-158, -4249, -3825, -225]], {'y': [11, 291, -31, -257], 'saturations': 0}], ['regression: random q15 fir 8', [[-249, 2187, 304], [24116, 6453, 6219, -174, -23699]], {'y': [-183, 1561, 607, 476, 226], 'saturations': 0}], ['regression: random q15 fir 5', [[21007, -2568, -1633], [-267, 32265, 7287, 49]], {'y': [-171, 20705, 2156, -2148], 'saturations': 0}], ['control: random q15 fir 16', [[-1447], [-227, 108]], {'y': [10, -5], 'saturations': 0}], ['control: random q15 fir 30', [[-1704], [264, 265, -20482, -11272]], {'y': [-14, -14, 1065, 586], 'saturations': 0}], ['control: small products lost', [[1000, 1000, 1000], [100, 100, 100, 100]], {'y': [3, 6, 9, 9], 'saturations': 0}], ['control: negative overflow', [[32767, 32767], [-32768, -32768]], {'y': [-32767, -32768], 'saturations': 1}]], [['regression: random q15 fir 10', [[17726], [-261, 178, -17320, 220]], {'y': [-141, 96, -9369, 119], 'saturations': 0}], ['regression: random q15 fir 11', [[-1511], [227, 17400]], {'y': [-10, -802], 'saturations': 0}], ['regression: random q15 fir 8', [[-249, 2187, 304], [24116, 6453, 6219, -174, -23699]], {'y': [-183, 1561, 607, 476, 226], 'saturations': 0}], ['control: positive overflow', [[32767, 32767], [32767, 32767]], {'y': [32766, 32767], 'saturations': 1}], ['control: asymmetric taps', [[16384, 8192, 0, -4096], [1000, 0, 0, 0, 0]], {'y': [500, 250, 0, -125, 0], 'saturations': 0}], ['control: random q15 fir 14', [[2191], [5955, -128]], {'y': [398, -9], 'saturations': 0}], ['control: random q15 fir 16', [[-1447], [-227, 108]], {'y': [10, -5], 'saturations': 0}]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression: exact positive rail | {'saturations': 0, 'y': [16383, 32766, 16383]} | {'saturations': 0, 'y': [16384, 32767, 16384]} | Failed |
| regression: exact rail then negative overflow | {'saturations': 0, 'y': [16383, 32766, 32765, -2]} | {'saturations': 0, 'y': [16384, 32767, 32766, -2]} | Failed |
| regression: random q15 fir 0 | {'saturations': 1, 'y': [-243, 25819, 32767, 9992]} | {'saturations': 1, 'y': [-243, 25820, 32767, 9993]} | Failed |
| control: small products lost | {'saturations': 0, 'y': [3, 6, 9, 9]} | {'saturations': 0, 'y': [3, 6, 9, 9]} | Passed |
| control: negative overflow | {'saturations': 1, 'y': [-32767, -32768]} | {'saturations': 1, 'y': [-32767, -32768]} | Passed |
| control: positive overflow | {'saturations': 1, 'y': [32766, 32767]} | {'saturations': 1, 'y': [32766, 32767]} | Passed |
| control: asymmetric taps | {'saturations': 0, 'y': [500, 250, 0, -125, 0]} | {'saturations': 0, 'y': [500, 250, 0, -125, 0]} | Passed |
SHA-256 / 34f29e8538f9bdf0612a5ac9041a8f76acecc66108aa6c145295d26e4fc8447c
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
coefs, xs = x
hist = [0] * len(coefs)
out = []
sat = 0
for v in xs:
hist = [v] + hist[:-1]
acc = sum((c * s + (1 << 14)) >> 15 for c, s in zip(coefs, hist))
y = acc
if y > 32767:
y = 32767
sat += 1
elif y < -32768:
y = -32768
sat += 1
out.append(y)
return {'y': out, 'saturations': sat}
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: exact positive rail', [[16384, 16384], [32767, 32767, 0]], {'y': [16384, 32767, 16384], 'saturations': 0}], ['regression: exact rail then negative overflow', [[16384, 16384, 32767], [32767, 32767, -32768, -32768]], {'y': [16384, 32767, 32766, -2], 'saturations': 0}], ['regression: random q15 fir 0', [[-32619, -22239, 2111], [244, -26104, -17179, -16]], {'y': [-243, 25820, 32767, 9993], 'saturations': 1}], ['control: small products lost', [[1000, 1000, 1000], [100, 100, 100, 100]], {'y': [3, 6, 9, 9], 'saturations': 0}], ['control: negative overflow', [[32767, 32767], [-32768, -32768]], {'y': [-32767, -32768], 'saturations': 1}], ['control: positive overflow', [[32767, 32767], [32767, 32767]], {'y': [32766, 32767], 'saturations': 1}], ['control: asymmetric taps', [[16384, 8192, 0, -4096], [1000, 0, 0, 0, 0]], {'y': [500, 250, 0, -125, 0], 'saturations': 0}]], [['regression: random q15 fir 1', [[-2422, 8115, -26238], [81, -19840, 164, 18, -31318, 26443]], {'y': [-6, 1487, -4990, 15926, 2188, -9725], 'saturations': 0}], ['regression: random q15 fir 2', [[-1074, -846, 29231], [-7102, 287, 18053, 24830, -286, 148]], {'y': [233, 174, -6935, -1024, 15473, 22152], 'saturations': 0}], ['regression: random q15 fir 0', [[-32619, -22239, 2111], [244, -26104, -17179, -16]], {'y': [-243, 25820, 32767, 9993], 'saturations': 1}], ['control: random q15 fir 14', [[2191], [5955, -128]], {'y': [398, -9], 'saturations': 0}], ['control: random q15 fir 16', [[-1447], [-227, 108]], {'y': [10, -5], 'saturations': 0}], ['control: random q15 fir 30', [[-1704], [264, 265, -20482, -11272]], {'y': [-14, -14, 1065, 586], 'saturations': 0}], ['control: small products lost', [[1000, 1000, 1000], [100, 100, 100, 100]], {'y': [3, 6, 9, 9], 'saturations': 0}]], [['regression: random q15 fir 4', [[375, 13267], [120, 1419, -3648]], {'y': [1, 65, 533], 'saturations': 0}], ['regression: random q15 fir 5', [[21007, -2568, -1633], [-267, 32265, 7287, 49]], {'y': [-171, 20705, 2156, -2148], 'saturations': 0}], ['regression: random q15 fir 2', [[-1074, -846, 29231], [-7102, 287, 18053, 24830, -286, 148]], {'y': [233, 174, -6935, -1024, 15473, 22152], 'saturations': 0}], ['control: negative overflow', [[32767, 32767], [-32768, -32768]], {'y': [-32767, -32768], 'saturations': 1}], ['control: positive overflow', [[32767, 32767], [32767, 32767]], {'y': [32766, 32767], 'saturations': 1}], ['control: asymmetric taps', [[16384, 8192, 0, -4096], [1000, 0, 0, 0, 0]], {'y': [500, 250, 0, -125, 0], 'saturations': 0}], ['control: random q15 fir 14', [[2191], [5955, -128]], {'y': [398, -9], 'saturations': 0}]], [['regression: random q15 fir 7', [[-2335, 2343], [-158, -4249, -3825, -225]], {'y': [11, 291, -31, -257], 'saturations': 0}], ['regression: random q15 fir 8', [[-249, 2187, 304], [24116, 6453, 6219, -174, -23699]], {'y': [-183, 1561, 607, 476, 226], 'saturations': 0}], ['regression: random q15 fir 5', [[21007, -2568, -1633], [-267, 32265, 7287, 49]], {'y': [-171, 20705, 2156, -2148], 'saturations': 0}], ['control: random q15 fir 16', [[-1447], [-227, 108]], {'y': [10, -5], 'saturations': 0}], ['control: random q15 fir 30', [[-1704], [264, 265, -20482, -11272]], {'y': [-14, -14, 1065, 586], 'saturations': 0}], ['control: small products lost', [[1000, 1000, 1000], [100, 100, 100, 100]], {'y': [3, 6, 9, 9], 'saturations': 0}], ['control: negative overflow', [[32767, 32767], [-32768, -32768]], {'y': [-32767, -32768], 'saturations': 1}]], [['regression: random q15 fir 10', [[17726], [-261, 178, -17320, 220]], {'y': [-141, 96, -9369, 119], 'saturations': 0}], ['regression: random q15 fir 11', [[-1511], [227, 17400]], {'y': [-10, -802], 'saturations': 0}], ['regression: random q15 fir 8', [[-249, 2187, 304], [24116, 6453, 6219, -174, -23699]], {'y': [-183, 1561, 607, 476, 226], 'saturations': 0}], ['control: positive overflow', [[32767, 32767], [32767, 32767]], {'y': [32766, 32767], 'saturations': 1}], ['control: asymmetric taps', [[16384, 8192, 0, -4096], [1000, 0, 0, 0, 0]], {'y': [500, 250, 0, -125, 0], 'saturations': 0}], ['control: random q15 fir 14', [[2191], [5955, -128]], {'y': [398, -9], 'saturations': 0}], ['control: random q15 fir 16', [[-1447], [-227, 108]], {'y': [10, -5], 'saturations': 0}]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression: exact positive rail | {'saturations': 1, 'y': [16384, 32767, 16384]} | {'saturations': 0, 'y': [16384, 32767, 16384]} | Failed |
| regression: exact rail then negative overflow | {'saturations': 1, 'y': [16384, 32767, 32766, -2]} | {'saturations': 0, 'y': [16384, 32767, 32766, -2]} | Failed |
| regression: random q15 fir 0 | {'saturations': 1, 'y': [-243, 25819, 32767, 9993]} | {'saturations': 1, 'y': [-243, 25820, 32767, 9993]} | Failed |
| control: small products lost | {'saturations': 0, 'y': [3, 6, 9, 9]} | {'saturations': 0, 'y': [3, 6, 9, 9]} | Passed |
| control: negative overflow | {'saturations': 1, 'y': [-32767, -32768]} | {'saturations': 1, 'y': [-32767, -32768]} | Passed |
| control: positive overflow | {'saturations': 1, 'y': [32766, 32767]} | {'saturations': 1, 'y': [32766, 32767]} | Passed |
| control: asymmetric taps | {'saturations': 0, 'y': [500, 250, 0, -125, 0]} | {'saturations': 0, 'y': [500, 250, 0, -125, 0]} | Passed |
SHA-256 / 9edeb825282933bf1ad6c83e25bf66d24108a0db0c35e46e8ca4f53ec7673f4d
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
coefs, xs = x
hist = [0] * len(coefs)
out = []
sat = 0
for v in xs:
hist = [v] + hist[:-1]
acc = sum(c * s for c, s in zip(coefs, hist))
y = (acc + (1 << 14)) >> 15
if y > 32767:
y = 32767
sat += 1
elif y < -32768:
y = -32768
sat += 1
out.append(y)
return {'y': out, 'saturations': sat}
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: exact positive rail', [[16384, 16384], [32767, 32767, 0]], {'y': [16384, 32767, 16384], 'saturations': 0}], ['regression: exact rail then negative overflow', [[16384, 16384, 32767], [32767, 32767, -32768, -32768]], {'y': [16384, 32767, 32766, -2], 'saturations': 0}], ['regression: random q15 fir 0', [[-32619, -22239, 2111], [244, -26104, -17179, -16]], {'y': [-243, 25820, 32767, 9993], 'saturations': 1}], ['control: small products lost', [[1000, 1000, 1000], [100, 100, 100, 100]], {'y': [3, 6, 9, 9], 'saturations': 0}], ['control: negative overflow', [[32767, 32767], [-32768, -32768]], {'y': [-32767, -32768], 'saturations': 1}], ['control: positive overflow', [[32767, 32767], [32767, 32767]], {'y': [32766, 32767], 'saturations': 1}], ['control: asymmetric taps', [[16384, 8192, 0, -4096], [1000, 0, 0, 0, 0]], {'y': [500, 250, 0, -125, 0], 'saturations': 0}]], [['regression: random q15 fir 1', [[-2422, 8115, -26238], [81, -19840, 164, 18, -31318, 26443]], {'y': [-6, 1487, -4990, 15926, 2188, -9725], 'saturations': 0}], ['regression: random q15 fir 2', [[-1074, -846, 29231], [-7102, 287, 18053, 24830, -286, 148]], {'y': [233, 174, -6935, -1024, 15473, 22152], 'saturations': 0}], ['regression: random q15 fir 0', [[-32619, -22239, 2111], [244, -26104, -17179, -16]], {'y': [-243, 25820, 32767, 9993], 'saturations': 1}], ['control: random q15 fir 14', [[2191], [5955, -128]], {'y': [398, -9], 'saturations': 0}], ['control: random q15 fir 16', [[-1447], [-227, 108]], {'y': [10, -5], 'saturations': 0}], ['control: random q15 fir 30', [[-1704], [264, 265, -20482, -11272]], {'y': [-14, -14, 1065, 586], 'saturations': 0}], ['control: small products lost', [[1000, 1000, 1000], [100, 100, 100, 100]], {'y': [3, 6, 9, 9], 'saturations': 0}]], [['regression: random q15 fir 4', [[375, 13267], [120, 1419, -3648]], {'y': [1, 65, 533], 'saturations': 0}], ['regression: random q15 fir 5', [[21007, -2568, -1633], [-267, 32265, 7287, 49]], {'y': [-171, 20705, 2156, -2148], 'saturations': 0}], ['regression: random q15 fir 2', [[-1074, -846, 29231], [-7102, 287, 18053, 24830, -286, 148]], {'y': [233, 174, -6935, -1024, 15473, 22152], 'saturations': 0}], ['control: negative overflow', [[32767, 32767], [-32768, -32768]], {'y': [-32767, -32768], 'saturations': 1}], ['control: positive overflow', [[32767, 32767], [32767, 32767]], {'y': [32766, 32767], 'saturations': 1}], ['control: asymmetric taps', [[16384, 8192, 0, -4096], [1000, 0, 0, 0, 0]], {'y': [500, 250, 0, -125, 0], 'saturations': 0}], ['control: random q15 fir 14', [[2191], [5955, -128]], {'y': [398, -9], 'saturations': 0}]], [['regression: random q15 fir 7', [[-2335, 2343], [-158, -4249, -3825, -225]], {'y': [11, 291, -31, -257], 'saturations': 0}], ['regression: random q15 fir 8', [[-249, 2187, 304], [24116, 6453, 6219, -174, -23699]], {'y': [-183, 1561, 607, 476, 226], 'saturations': 0}], ['regression: random q15 fir 5', [[21007, -2568, -1633], [-267, 32265, 7287, 49]], {'y': [-171, 20705, 2156, -2148], 'saturations': 0}], ['control: random q15 fir 16', [[-1447], [-227, 108]], {'y': [10, -5], 'saturations': 0}], ['control: random q15 fir 30', [[-1704], [264, 265, -20482, -11272]], {'y': [-14, -14, 1065, 586], 'saturations': 0}], ['control: small products lost', [[1000, 1000, 1000], [100, 100, 100, 100]], {'y': [3, 6, 9, 9], 'saturations': 0}], ['control: negative overflow', [[32767, 32767], [-32768, -32768]], {'y': [-32767, -32768], 'saturations': 1}]], [['regression: random q15 fir 10', [[17726], [-261, 178, -17320, 220]], {'y': [-141, 96, -9369, 119], 'saturations': 0}], ['regression: random q15 fir 11', [[-1511], [227, 17400]], {'y': [-10, -802], 'saturations': 0}], ['regression: random q15 fir 8', [[-249, 2187, 304], [24116, 6453, 6219, -174, -23699]], {'y': [-183, 1561, 607, 476, 226], 'saturations': 0}], ['control: positive overflow', [[32767, 32767], [32767, 32767]], {'y': [32766, 32767], 'saturations': 1}], ['control: asymmetric taps', [[16384, 8192, 0, -4096], [1000, 0, 0, 0, 0]], {'y': [500, 250, 0, -125, 0], 'saturations': 0}], ['control: random q15 fir 14', [[2191], [5955, -128]], {'y': [398, -9], 'saturations': 0}], ['control: random q15 fir 16', [[-1447], [-227, 108]], {'y': [10, -5], 'saturations': 0}]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression: exact positive rail | {'saturations': 0, 'y': [16384, 32767, 16384]} | {'saturations': 0, 'y': [16384, 32767, 16384]} | Passed |
| regression: exact rail then negative overflow | {'saturations': 0, 'y': [16384, 32767, 32766, -2]} | {'saturations': 0, 'y': [16384, 32767, 32766, -2]} | Passed |
| regression: random q15 fir 0 | {'saturations': 1, 'y': [-243, 25820, 32767, 9993]} | {'saturations': 1, 'y': [-243, 25820, 32767, 9993]} | Passed |
| control: small products lost | {'saturations': 0, 'y': [3, 6, 9, 9]} | {'saturations': 0, 'y': [3, 6, 9, 9]} | Passed |
| control: negative overflow | {'saturations': 1, 'y': [-32767, -32768]} | {'saturations': 1, 'y': [-32767, -32768]} | Passed |
| control: positive overflow | {'saturations': 1, 'y': [32766, 32767]} | {'saturations': 1, 'y': [32766, 32767]} | Passed |
| control: asymmetric taps | {'saturations': 0, 'y': [500, 250, 0, -125, 0]} | {'saturations': 0, 'y': [500, 250, 0, -125, 0]} | Passed |
SHA-256 / 828798186d7d7d4b637bef5ef65aaa4269d5d9c29f6ab9fa4ec15e85ae9afac0
Verification & scope
A deterministic bounded teaching model with a stipulated toy contract; exact rational arithmetic or fixed-decimal rounding keeps outputs strict JSON. It is not a production DSP library and claims no standards conformance. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:51:39.407242+00:00.
Case digest / a94d1b1bfcee71ba42b62a5aeb0fc3b7e24c223c6acc350fd43a656e3d5341b9