\n", " val x_n1 = RegNext(io.in, 0.U)\n", " val x_n2 = RegNext(x_n1, 0.U)\n", " val x_n3 = RegNext(x_n2, 0.U)\n", " io.out := io.in * b0.U(8.W) + \n", " x_n1 * b1.U(8.W) +\n", " x_n2 * b2.U(8.W) + \n", " x_n3 * b3.U(8.W)\n", "
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Module 2.5: Putting it all Together: An FIR Filter\n",
"**Prev: [Sequential Logic](2.4_sequential_logic.ipynb)**
\n",
"**Next: [ChiselTest (was chisel-testers2)](2.6_chiseltest.ipynb)**\n",
"\n",
"## Motivation\n",
"Now that you've learned the basics of Chisel, let's use that knowledge to build a FIR (finite impulse response) filter module! FIR filters are very common in digital signal processing applications. Also, the FIR filter will reappear frequently in module 3, so it's important that you don't filter out this module by skipping ahead! If you are unfamiliar with FIR filters, head over to the article on [trusty Wikipedia](https://en.wikipedia.org/wiki/Finite_impulse_response) to learn more.\n",
"\n",
"## Setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"val path = System.getProperty(\"user.dir\") + \"/source/load-ivy.sc\"\n",
"interp.load.module(ammonite.ops.Path(java.nio.file.FileSystems.getDefault().getPath(path)))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import chisel3._\n",
"import chisel3.util._\n",
"import chisel3.tester._\n",
"import chisel3.tester.RawTester.test"
]
},
{
"cell_type": "markdown",
"metadata": {
"nbpresent": {
"id": "2e849671-a1e9-48b2-9bba-eb916ac623c6"
}
},
"source": [
"---\n",
"# FIR Filter\n",
"\n",
"The FIR filter you will design performs the following operation.\n",
"\n",
"\n",
"\n",
"Basically, this does a elementwise multiplication of the element of the filter coefficients with the elements of the input signal and outputs the sum (also called a _convolution_).\n",
"\n",
"Or, a signals definition:\n",
"\n",
"$y[n] = b_0 x[n] + b_1 x[n-1] + b_2 x[n-2] + ...$\n",
" - $y[n]$ is the output signal at time $n$\n",
" - $x[n]$ is the input signal\n",
" - $b_i$ are the filter coefficients or impulse response\n",
" - $n-1$, $n-2$, ... are time $n$ delayed by 1, 2, ... cycles\n",
" \n",
"## 8-bit Specification\n",
"\n",
"Build a 4-element FIR filter where the four filter coefficients are parameters. A module skeleton and basic tests are provided for you.\n",
"Note that both the input and output are 8-bit unsigned integers. You will need to save necessary state (like delayed signal values) using constructs like shift registers. Use the provided testers to check your implementation.\n",
"Registers with constant inputs can be created using a `ShiftRegister` of shift value 1, or by using the `RegNext` construct.\n",
"\n",
"Note: for the tests to pass, your registers must be initialized to `0.U`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"nbpresent": {
"id": "26e4a686-0397-4306-985c-813909256c95"
}
},
"outputs": [],
"source": [
"class My4ElementFir(b0: Int, b1: Int, b2: Int, b3: Int) extends Module {\n",
" val io = IO(new Bundle {\n",
" val in = Input(UInt(8.W))\n",
" val out = Output(UInt(8.W))\n",
" })\n",
"\n",
" ???\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"nbpresent": {
"id": "ddf24b7b-09a2-46f0-b1d8-cb2ca7976b4b"
}
},
"outputs": [],
"source": [
"// Simple sanity check: a element with all zero coefficients should always produce zero\n",
"test(new My4ElementFir(0, 0, 0, 0)) { c =>\n",
" c.io.in.poke(0.U)\n",
" c.io.out.expect(0.U)\n",
" c.clock.step(1)\n",
" c.io.in.poke(4.U)\n",
" c.io.out.expect(0.U)\n",
" c.clock.step(1)\n",
" c.io.in.poke(5.U)\n",
" c.io.out.expect(0.U)\n",
" c.clock.step(1)\n",
" c.io.in.poke(2.U)\n",
" c.io.out.expect(0.U)\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"// Simple 4-point moving average\n",
"test(new My4ElementFir(1, 1, 1, 1)) { c =>\n",
" c.io.in.poke(1.U)\n",
" c.io.out.expect(1.U) // 1, 0, 0, 0\n",
" c.clock.step(1)\n",
" c.io.in.poke(4.U)\n",
" c.io.out.expect(5.U) // 4, 1, 0, 0\n",
" c.clock.step(1)\n",
" c.io.in.poke(3.U)\n",
" c.io.out.expect(8.U) // 3, 4, 1, 0\n",
" c.clock.step(1)\n",
" c.io.in.poke(2.U)\n",
" c.io.out.expect(10.U) // 2, 3, 4, 1\n",
" c.clock.step(1)\n",
" c.io.in.poke(7.U)\n",
" c.io.out.expect(16.U) // 7, 2, 3, 4\n",
" c.clock.step(1)\n",
" c.io.in.poke(0.U)\n",
" c.io.out.expect(12.U) // 0, 7, 2, 3\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"// Nonsymmetric filter\n",
"test(new My4ElementFir(1, 2, 3, 4)) { c =>\n",
" c.io.in.poke(1.U)\n",
" c.io.out.expect(1.U) // 1*1, 0*2, 0*3, 0*4\n",
" c.clock.step(1)\n",
" c.io.in.poke(4.U)\n",
" c.io.out.expect(6.U) // 4*1, 1*2, 0*3, 0*4\n",
" c.clock.step(1)\n",
" c.io.in.poke(3.U)\n",
" c.io.out.expect(14.U) // 3*1, 4*2, 1*3, 0*4\n",
" c.clock.step(1)\n",
" c.io.in.poke(2.U)\n",
" c.io.out.expect(24.U) // 2*1, 3*2, 4*3, 1*4\n",
" c.clock.step(1)\n",
" c.io.in.poke(7.U)\n",
" c.io.out.expect(36.U) // 7*1, 2*2, 3*3, 4*4\n",
" c.clock.step(1)\n",
" c.io.in.poke(0.U)\n",
" c.io.out.expect(32.U) // 0*1, 7*2, 2*3, 3*4\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"
\n", " val x_n1 = RegNext(io.in, 0.U)\n", " val x_n2 = RegNext(x_n1, 0.U)\n", " val x_n3 = RegNext(x_n2, 0.U)\n", " io.out := io.in * b0.U(8.W) + \n", " x_n1 * b1.U(8.W) +\n", " x_n2 * b2.U(8.W) + \n", " x_n3 * b3.U(8.W)\n", "