Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
246 lines (221 sloc) 6.61 KB
////////////////////////////////////////////////////////////////////////////////
//
// Filename: slowfil.v
//
// Project: DSP Filtering Example Project
//
// Purpose: Unlike fastfir.v and genericfir.v, both of which require one
// hardware multiply element per tap, this slowfil design requires
// only one multiply element in total. It is useful for those times and
// cases when there are fewer taps than there are clock intervals between
// incoming samples. In all other respects, however, it remains quite
// generic.
//
// Creator: Dan Gisselquist, Ph.D.
// Gisselquist Technology, LLC
//
////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2017-2018, Gisselquist Technology, LLC
//
// This file is part of the DSP filtering set of designs.
//
// The DSP filtering designs are free RTL designs: you can redistribute them
// and/or modify any of them under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// The DSP filtering designs are distributed in the hope that they will be
// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
// General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with these designs. (It's in the $(ROOT)/doc directory. Run make
// with no target there if the PDF file isn't present.) If not, see
// <http://www.gnu.org/licenses/> for a copy.
//
// License: LGPL, v3, as defined and found on www.gnu.org,
// http://www.gnu.org/licenses/lgpl.html
//
////////////////////////////////////////////////////////////////////////////////
//
//
`default_nettype none
//
module slowfil(i_clk, i_reset, i_tap_wr, i_tap, i_ce, i_sample, o_ce, o_result);
parameter LGNTAPS = 7, IW=16, TW=16, OW = IW+TW+LGNTAPS;
parameter [LGNTAPS:0] NTAPS = 110; // (1<<LGNTAPS);
parameter [0:0] FIXED_TAPS = 1'b0;
parameter INITIAL_COEFFS = "";
localparam MEMSZ = (1<<LGNTAPS);
//
// Control inputs (wires)
input wire i_clk, i_reset;
//
// Coefficient control -- allows you to update coefficients in the
// filter
input wire i_tap_wr;
input wire [(TW-1):0] i_tap;
//
// New sample input(s)--a new sample comes in any time i_ce is true.
// There must be at least NTAPS idle's between every pair of valid
// i_ce's.
input wire i_ce;
input wire [(IW-1):0] i_sample;
//
// The output--valid any time o_ce is true. Since it only changes
// once per interval, you can ignore the o_ce line if you choose and
// just use i_ce.
output reg o_ce;
output reg [(OW-1):0] o_result;
//
//
reg [(TW-1):0] tapmem [0:(MEMSZ-1)]; // Coef memory
reg signed [(TW-1):0] tap; // Value read from coef memory
reg [(LGNTAPS-1):0] dwidx, didx; // Data write and read indices
reg [(LGNTAPS-1):0] tidx; // Coefficient read index
reg [(IW-1):0] dmem [0:(MEMSZ-1)]; // Data memory
reg signed [(IW-1):0] data; // Data value read from memory
// Traveling CE values
reg d_ce, p_ce, m_ce;
//
// The product and accumulator values for the filter
reg signed [(IW+TW-1):0] product;
reg signed [(OW-1):0] r_acc;
//
//
// Allow the user to set the taps
//
//
// Starting at zero on reset, increment the tap write index on any
// write of a new tap. This also means that changing coefficients
// will require a reset.
generate if (FIXED_TAPS)
begin
initial $readmemh(INITIAL_COEFFS, tapmem);
// Make Verilators -Wall happy
// Verilator lint_off UNUSED
wire [TW:0] ignored_inputs;
assign ignored_inputs = { i_tap_wr, i_tap };
// Verilator lint_on UNUSED
end else begin
// Coef memory write index
reg [(LGNTAPS-1):0] tapwidx;
initial tapwidx = 0;
always @(posedge i_clk)
if(i_reset)
tapwidx <= 0;
else if (i_tap_wr)
tapwidx <= tapwidx + 1'b1;
if (INITIAL_COEFFS != 0)
initial $readmemh(INITIAL_COEFFS, tapmem);
always @(posedge i_clk)
if (i_tap_wr)
tapmem[tapwidx] <= i_tap;
end endgenerate
//
//
// Record the incoming data into a local memory
//
//
// Notice how this data writing section is *independent* of the reset,
// depending only upon new sample data.
initial dwidx = 0;
always @(posedge i_clk)
if (i_ce)
dwidx <= dwidx + 1'b1;
always @(posedge i_clk)
if (i_ce)
dmem[dwidx] <= i_sample;
//
//
// Calculate the indexes of the filter table
//
//
// Determine if the next clock (not this one) will contain the last
// valid index, and so whether or not we need to stop.
wire last_tap_index;
assign last_tap_index = (NTAPS[LGNTAPS-1:0]-tidx <= 1);
// The pre_acc_ce traveling CE values keep track of when the
// results of reading memory are valid at the accumulation section
// of this code later on.
reg [2:0] pre_acc_ce;
initial pre_acc_ce = 3'h0;
always @(posedge i_clk)
if (i_reset)
pre_acc_ce[0] <= 1'b0;
else if (i_ce)
pre_acc_ce[0] <= 1'b1;
else if ((pre_acc_ce[0])&&(!last_tap_index))
pre_acc_ce[0] <= 1'b1;
else
pre_acc_ce[0] <= 1'b0;
// pre_acc_ce[0] means that the tap index is valid
// pre_acc_ce[1] means that the tap value is valid
// pre_acc_ce[2] means that the product is valid
always @(posedge i_clk)
if (i_reset)
pre_acc_ce[2:1] <= 2'b0;
else
pre_acc_ce[2:1] <= pre_acc_ce[1:0];
initial didx = 0;
initial tidx = 0;
always @(posedge i_clk)
if (i_ce)
begin
didx <= dwidx;
tidx <= 0;
end else begin
didx <= didx - 1'b1;
tidx <= tidx + 1'b1;
end
// m_ce is valid when the first index is valid
initial m_ce = 1'b0;
always @(posedge i_clk)
m_ce <= (i_ce)&&(!i_reset);
//
//
// Read from memory cycle
//
//
initial tap = 0;
always @(posedge i_clk)
tap <= tapmem[tidx[(LGNTAPS-1):0]];
initial data = 0;
always @(posedge i_clk)
data <= dmem[didx];
// d_ce is valid when the first data from memory is read/valid
initial d_ce = 0;
always @(posedge i_clk)
d_ce <= (m_ce)&&(!i_reset);
//
// Apply the product to the tap and data just read
//
// p_ce is valid on the first valid product
initial p_ce = 1'b0;
always @(posedge i_clk)
p_ce <= (d_ce)&&(!i_reset);
initial product = 0;
always @(posedge i_clk)
product <= tap * data;
initial r_acc = 0;
always @(posedge i_clk)
if (p_ce)
r_acc <={ {(OW-(IW+TW)){product[(IW+TW-1)]}}, product };
else if (pre_acc_ce[2])
r_acc <= r_acc + { {(OW-(IW+TW)){product[(IW+TW-1)]}},
product };
//
//
// Copy the result to the output
//
//
initial o_result = 0;
always @(posedge i_clk)
if (p_ce)
o_result <= r_acc;
initial o_ce = 1'b0;
always @(posedge i_clk)
o_ce <= (p_ce)&&(!i_reset);
endmodule