-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added fixed point Finite Impulse Response filter.
Fully pipelined FIR. Signed-off-by: Kevin Joly <[email protected]>
- Loading branch information
Showing
5 changed files
with
452 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# FIR Filter | ||
|
||
Simple fixed point FIR filter with pipelined computation. | ||
|
||
Tests are run as follow: | ||
```sbt "testOnly chisel.lib.firfilter.SimpleFIRFilterTest -- -DwriteVcd=1"``` | ||
```sbt "testOnly chisel.lib.firfilter.RandomSignalTest -- -DwriteVcd=1"``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
/* | ||
* | ||
* A fixed point FIR filter module. | ||
* | ||
* Author: Kevin Joly ([email protected]) | ||
* | ||
*/ | ||
|
||
package chisel.lib.firfilter | ||
|
||
import chisel3._ | ||
import chisel3.experimental.ChiselEnum | ||
import chisel3.util._ | ||
|
||
/* | ||
* FIR filter module | ||
* | ||
* Apply filter on input samples passed by ready/valid handshake. Coefficients | ||
* are to be set prior to push any input sample. | ||
* | ||
* All the computations are done in fixed point. Output width is inputWidth + | ||
* coefWidth + log2Ceil(coefNum). | ||
* | ||
*/ | ||
class FIRFilter( | ||
inputWidth: Int, | ||
coefWidth: Int, | ||
coefDecimalWidth: Int, | ||
coefNum: Int) | ||
extends Module { | ||
|
||
val outputWidth = inputWidth + coefWidth + log2Ceil(coefNum) | ||
|
||
val io = IO(new Bundle { | ||
/* | ||
* Input samples | ||
*/ | ||
val input = Flipped(Decoupled(SInt(inputWidth.W))) | ||
/* | ||
* Filter's coefficients b[0], b[1], ... | ||
*/ | ||
val coef = Input(Vec(coefNum, SInt(coefWidth.W))) | ||
/* | ||
* Filtered samples. Fixed point format is: | ||
* (inputWidth+coefWidth).coefDecimalWidth | ||
* Thus, output should be right shifted to the right of 'coefDecimalWidth' bits. | ||
*/ | ||
val output = Decoupled(SInt(outputWidth.W)) | ||
}) | ||
|
||
assert(coefWidth >= coefDecimalWidth) | ||
|
||
val coefIdx = RegInit(0.U(coefNum.W)) | ||
|
||
object FIRFilterState extends ChiselEnum { | ||
val Idle, Compute, Valid, LeftOver = Value | ||
} | ||
|
||
val state = RegInit(FIRFilterState.Idle) | ||
|
||
switch(state) { | ||
is(FIRFilterState.Idle) { | ||
when(io.input.valid) { | ||
state := FIRFilterState.Compute | ||
} | ||
} | ||
is(FIRFilterState.Compute) { | ||
when(coefIdx === (coefNum - 1).U) { | ||
state := FIRFilterState.LeftOver | ||
} | ||
} | ||
is(FIRFilterState.LeftOver) { | ||
state := FIRFilterState.Valid | ||
} | ||
is(FIRFilterState.Valid) { | ||
when(io.output.ready) { | ||
state := FIRFilterState.Idle | ||
} | ||
} | ||
} | ||
|
||
when((state === FIRFilterState.Idle) && io.input.valid) { | ||
coefIdx := 1.U | ||
}.elsewhen(state === FIRFilterState.Compute) { | ||
when(coefIdx === (coefNum - 1).U) { | ||
coefIdx := 0.U | ||
}.otherwise { | ||
coefIdx := coefIdx + 1.U | ||
} | ||
}.otherwise { | ||
coefIdx := 0.U | ||
} | ||
|
||
val inputReg = RegInit(0.S(inputWidth.W)) | ||
val inputMem = Mem(coefNum - 1, SInt(inputWidth.W)) | ||
val inputMemAddr = RegInit(0.U(math.max(log2Ceil(coefNum - 1), 1).W)) | ||
val inputMemOut = Wire(SInt(inputWidth.W)) | ||
val inputRdWr = inputMem(inputMemAddr) | ||
|
||
inputMemOut := DontCare | ||
|
||
when(state === FIRFilterState.LeftOver) { | ||
inputRdWr := inputReg | ||
}.elsewhen((state === FIRFilterState.Idle) && io.input.valid) { | ||
inputReg := io.input.bits // Delayed write | ||
inputMemOut := inputRdWr | ||
}.otherwise { | ||
inputMemOut := inputRdWr | ||
} | ||
|
||
when((state === FIRFilterState.Compute) && (coefIdx < (coefNum - 1).U)) { | ||
when(inputMemAddr === (coefNum - 2).U) { | ||
inputMemAddr := 0.U | ||
}.otherwise { | ||
inputMemAddr := inputMemAddr + 1.U | ||
} | ||
} | ||
|
||
val inputSum = RegInit(0.S(outputWidth.W)) | ||
|
||
val multNumOut = Wire(SInt((inputWidth + coefWidth).W)) | ||
val multNumOutReg = RegInit(0.S((inputWidth + coefWidth).W)) | ||
val multNumIn = Wire(SInt(inputWidth.W)) | ||
|
||
when((state === FIRFilterState.Idle) && io.input.valid) { | ||
multNumOutReg := multNumOut | ||
inputSum := 0.S | ||
}.elsewhen(state === FIRFilterState.Compute) { | ||
when(coefIdx < coefNum.U) { | ||
multNumOutReg := multNumOut | ||
inputSum := inputSum +& multNumOutReg | ||
} | ||
}.elsewhen(state === FIRFilterState.LeftOver) { | ||
inputSum := inputSum +& multNumOutReg | ||
} | ||
|
||
when(state === FIRFilterState.Idle) { | ||
multNumIn := io.input.bits | ||
}.otherwise { | ||
multNumIn := inputMemOut | ||
} | ||
|
||
multNumOut := multNumIn * io.coef(coefIdx) | ||
|
||
io.input.ready := state === FIRFilterState.Idle | ||
io.output.valid := state === FIRFilterState.Valid | ||
io.output.bits := inputSum | ||
} |
131 changes: 131 additions & 0 deletions
131
src/test/scala/chisel/lib/firfilter/RandomSignalTest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
/* | ||
* Filter a random signal using FIRFilter module and compare with the expected output. | ||
* | ||
* See README.md for license details. | ||
*/ | ||
|
||
package chisel.lib.firfilter | ||
|
||
import chisel3._ | ||
import chisel3.experimental.VecLiterals._ | ||
import chisel3.util.log2Ceil | ||
import chiseltest._ | ||
|
||
import org.scalatest.flatspec.AnyFlatSpec | ||
import org.scalatest.matchers.should.Matchers | ||
|
||
import scala.util.Random | ||
|
||
trait FIRFilterBehavior { | ||
|
||
this: AnyFlatSpec with ChiselScalatestTester => | ||
|
||
def testFilter( | ||
inputWidth: Int, | ||
inputDecimalWidth: Int, | ||
coefWidth: Int, | ||
coefDecimalWidth: Int, | ||
coefs: Seq[Int], | ||
inputData: Seq[Int], | ||
expectedOutput: Seq[Double], | ||
precision: Double | ||
): Unit = { | ||
|
||
it should "work" in { | ||
test( | ||
new FIRFilter( | ||
inputWidth = inputWidth, | ||
coefWidth = coefWidth, | ||
coefDecimalWidth = coefDecimalWidth, | ||
coefNum = coefs.length, | ||
) | ||
) { dut => | ||
dut.io.coef.poke(Vec.Lit(coefs.map(_.S(coefWidth.W)): _*)) | ||
|
||
dut.io.output.ready.poke(true.B) | ||
|
||
for ((d, e) <- (inputData.zip(expectedOutput))) { | ||
|
||
dut.io.input.ready.expect(true.B) | ||
|
||
// Push input sample | ||
dut.io.input.bits.poke(d.S(inputWidth.W)) | ||
dut.io.input.valid.poke(true.B) | ||
|
||
dut.clock.step(1) | ||
|
||
dut.io.input.valid.poke(false.B) | ||
|
||
for (i <- 0 until coefs.length) { | ||
dut.io.output.valid.expect(false.B) | ||
dut.io.input.ready.expect(false.B) | ||
dut.clock.step(1) | ||
} | ||
|
||
// Check output | ||
val outputDecimalWidth = coefDecimalWidth + inputDecimalWidth | ||
val output = dut.io.output.bits.peek().litValue.toFloat / math.pow(2, outputDecimalWidth).toFloat | ||
val upperBound = e + precision | ||
val lowerBound = e - precision | ||
|
||
assert(output < upperBound) | ||
assert(output > lowerBound) | ||
|
||
dut.io.output.valid.expect(true.B) | ||
|
||
dut.clock.step(1) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
class RandomSignalTest extends AnyFlatSpec with FIRFilterBehavior with ChiselScalatestTester with Matchers { | ||
|
||
def computeExpectedOutput(coefs: Seq[Double], inputData: Seq[Double]): Seq[Double] = { | ||
return for (i <- 0 until inputData.length) yield { | ||
val inputSum = (for (j <- i until math.max(i - coefs.length, -1) by -1) yield { | ||
inputData(j) * coefs(i - j) | ||
}).reduce(_ + _) | ||
|
||
inputSum | ||
} | ||
} | ||
|
||
behavior.of("FIRFilter") | ||
|
||
Random.setSeed(11340702) | ||
|
||
// 9 taps Kaiser high-pass filter 50Hz (sampling freq: 44.1kHz) | ||
val coefs = Seq(-0.00227242, -0.00227255, -0.00227265, -0.00227271, 0.99999962, -0.00227271, -0.00227265, -0.00227255, -0.00227242) | ||
|
||
// Setup data width | ||
val inputWidth = 16 | ||
val inputDecimalWidth = 12 | ||
|
||
val coefWidth = 32 | ||
val coefDecimalWidth = 28 | ||
|
||
// Generate random input data [-1., 1.] | ||
val inputData = Seq.fill(100)(-1.0 + Random.nextDouble * 2.0) | ||
|
||
// Compute expected outputs | ||
val expectedOutput = computeExpectedOutput(coefs, inputData) | ||
|
||
// Floating point to fixed point data | ||
val coefsInt = for (n <- coefs) yield { (n * math.pow(2, coefDecimalWidth)).toInt } | ||
val inputDataInt = for (x <- inputData) yield (x * math.pow(2, inputDecimalWidth)).toInt | ||
|
||
(it should behave).like( | ||
testFilter( | ||
inputWidth, | ||
inputDecimalWidth, | ||
coefWidth, | ||
coefDecimalWidth, | ||
coefsInt, | ||
inputDataInt, | ||
expectedOutput, | ||
0.0005 | ||
) | ||
) | ||
} |
Oops, something went wrong.