blob: 2bc8942e426babcea2f6370c5b2412bbe81de14d [file] [log] [blame] [edit]
////////////////////////////////////////////////////////////////////////////////
//
// Filename: slowsymf.v
//
// Project: DSP Filtering Example Project
//
// Purpose: This module implements a slow, symmetric FIR filter. The
// cofficients can be either fixed or dynamically set. This
// implementation exploits the symmetry in the symmetric filter to use
// half as many multiplies as the slowfil.v module in this same
// repository. It has the same calling convention as that one.
//
// Creator: Dan Gisselquist, Ph.D.
// Gisselquist Technology, LLC
//
////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2018-2019, 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 slowsymf(i_clk, i_reset, i_tap_wr, i_tap, i_ce, i_sample, o_ce, o_result);
parameter LGNTAPS = 7, IW=16, TW=12,
OW = IW+TW+LGNTAPS;
parameter [LGNTAPS:0] NTAPS = 107;
parameter [0:0] FIXED_TAPS = 1'b0;
parameter INITIAL_COEFFS = "";
// // //
localparam LGNMEM = LGNTAPS-1;
localparam [LGNTAPS-1:0] HALFTAPS = NTAPS[LGNTAPS:1];
localparam MEMSZ = (1<<LGNMEM);
//
// 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 [(LGNMEM-1):0] dwidx, lidx, ridx;// Data write and read indices
reg [(LGNMEM-1):0] tidx; // Coefficient read index
reg [(IW-1):0] dmem1 [0:(MEMSZ-1)]; // Data memory
reg [(IW-1):0] dmem2 [0:(MEMSZ-1)]; // Data memory
// Data value read from memory, and then summed together
reg signed [(IW-1):0] dleft, dright, mid_sample;
reg signed [IW:0] dsum;
// Traveling CE values
reg m_ce, d_ce, s_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 : SET_FIXED_TAPS
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 : DYNAMIC_TAP_ADJUSTMENT
// Coef memory write index
reg [(LGNMEM-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)
dmem1[dwidx] <= i_sample;
always @(posedge i_clk)
if (i_ce)
dmem2[dwidx] <= mid_sample;
always @(posedge i_clk)
if (i_reset)
mid_sample <= 0;
else if (i_ce)
mid_sample <= dleft;
//
//
// 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, last_data_index;
wire [LGNTAPS-2:0] taps_left;
assign taps_left = (NTAPS[LGNTAPS-1:1]-tidx);
assign last_tap_index = (taps_left <= 1);
assign last_data_index= (NTAPS[LGNTAPS-1:1]-tidx <= 2);
// 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 [3:0] pre_acc_ce;
initial pre_acc_ce = 4'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 if (!m_ce)
pre_acc_ce[0] <= 1'b0;
// pre_acc_ce[0] means the data index is valid
// pre_acc_ce[1] means the data values are valid, tap index is valid
// pre_acc_ce[2] means the data sum and tap value are valid
// pre_acc_ce[3] means that the product is valid
always @(posedge i_clk)
if (i_reset)
pre_acc_ce[3:1] <= 3'b0;
else
pre_acc_ce[3:1] <= { pre_acc_ce[2:1],
((m_ce)||((pre_acc_ce[0])&&(!last_tap_index))) };
initial lidx = 0;
initial ridx = 0;
always @(posedge i_clk)
if (i_reset)
begin
lidx <= 0;
ridx <= 0;
end else if (i_ce)
begin
lidx <= dwidx; // Newest value
ridx <= dwidx-(HALFTAPS[LGNMEM-1:0])+1;
end else if ((m_ce)||(!last_data_index))
begin
lidx <= lidx - 1'b1;
ridx <= ridx + 1'b1;
end
initial tidx = 0;
always @(posedge i_clk)
if (i_reset)
tidx <= 0;
else if (m_ce)
tidx <= 0;
else if (!last_tap_index)
tidx <= tidx + 1'b1;
//
//
// Read from memory cycle
//
//
//
// m_ce is the memory strobe. It is true when the first index is valid
initial m_ce = 1'b0;
always @(posedge i_clk)
m_ce <= (i_ce)&&(!i_reset);
initial dleft = 0;
initial dright = 0;
always @(posedge i_clk)
begin
// dleft <= dmem1[ didx[LGNMEM-1:0]];
// dright <= dmem2[~didx[LGNMEM-1:0]];
dleft <= dmem1[lidx];
dright <= dmem2[ridx];
end
//
// Summation cycle, and read coefficient from memory
//
// 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);
initial tap = 0;
always @(posedge i_clk)
tap <= tapmem[tidx[(LGNTAPS-2):0]];
initial dsum = 0;
always @(posedge i_clk)
if (i_reset)
dsum <= 0;
else
dsum <= dleft + dright;
// s_ce is valid when the first data sum is valid
initial s_ce = 0;
always @(posedge i_clk)
s_ce <= (d_ce)&&(!i_reset);
//
// Apply the product to the tap and data just read
//
initial product = 0;
always @(posedge i_clk)
product <= tap * dsum;
reg [OW-1:0] midprod;
initial midprod = 0;
always @(posedge i_clk)
if (i_reset)
midprod <= 0;
else if (m_ce)
midprod <= { {(OW-IW-TW+1){mid_sample[IW-1]}},
mid_sample, {(TW-1){1'b0}}}
- {{(OW-IW){mid_sample[IW-1]}}, mid_sample};
initial r_acc = 0;
always @(posedge i_clk)
if (i_reset)
r_acc <= 0;
else if (s_ce)
r_acc <= midprod;
else if (pre_acc_ce[3])
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 (s_ce)
o_result <= r_acc;
initial o_ce = 1'b0;
always @(posedge i_clk)
o_ce <= (s_ce)&&(!i_reset);
endmodule