blob: 9ea3b9b09307cbe9adc449f62dd3f327943d52a6 [file] [log] [blame] [edit]
////////////////////////////////////////////////////////////////////////////////
//
// Filename: boxcar.v
//
// Project: DSP Filtering Example Project
//
// Purpose: This filter expands upon the capabilities of the simplest.v
// non-trivial filter. Like simplest.v, this filter only averages
// samples together. Unlike the simplest.v filter, this filter will
// average more than two samples together. Indeed, the number of samples
// that can be averaged together is programmable from 1 to
// (1<<LGMEM)-1. To change the number of averages, raise the reset signal,
// i_reset, and set i_navg while i_reset is set. Upon clearing i_reset,
// the right number of taps will be set.
//
// Algorithm:
//
// y[n] = SUM_{k=0}^{navg-1} x[n-k]
// = y[n-1] + x[n] - x[n-navg]
//
// We'll use block RAM to hold the x[n-navg] value.
//
// Pipeline scheduling: The following variables are set on the given pipeline
// stages:
//
// (PRE) navg (set on reset)
//
// 0 rdaddr
// 1 mem[rdaddr]
// 2 memval
// ival
// full
// wraddr
// 3 sub
// mem[wraddr]
// 4 acc
// 5 rounded, and thence o_result
//
// The overall delay of this algorithm is four samples. One to clock in
// the input, and three more to get to where the input has an effect.
//
// Creator: Dan Gisselquist, Ph.D.
// Gisselquist Technology, LLC
//
////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2017-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 boxcar(i_clk, i_reset, i_navg, i_ce, i_sample, o_result);
parameter IW=16, // Input bit-width
LGMEM=6, // Size of the memory.
OW=(IW+LGMEM); // Output bit-width
parameter [0:0] FIXED_NAVG=1'b0; // True if number of averages is fixed
parameter [0:0] OPT_SIGNED=1'b1; // True for averaging signed numbers
// Always assume we'll be averaging by the maximum amount, unless told
// otherwise. Minus one, in two's complement, will become this number
// when interpreted as an unsigned number.
parameter [(LGMEM-1):0] INITIAL_NAVG= -1; // Initial avglen
input wire i_clk, // Data clock
i_reset;// Positive synchronous reset
input wire [(LGMEM-1):0] i_navg; // Requested number of averages
//
input wire i_ce; // True if i_sample is valid
input wire [(IW-1):0] i_sample;// Input value to be filtered
output reg [(OW-1):0] o_result;// Output filter value
reg full;
reg [(LGMEM-1):0] rdaddr, wraddr;
reg [(IW-1):0] mem [0:((1<<LGMEM)-1)];
reg [(IW-1):0] preval, memval;
reg [IW:0] sub;
reg [(IW+LGMEM-1):0] acc;
// The write address. We'll write into our memory using this address.
// It starts at zero, and increments on every valid sample.
initial wraddr = 0;
always @(posedge i_clk)
if (i_reset)
wraddr <= 0;
else if (i_ce)
wraddr <= wraddr + 1'b1;
// Calculate the requested number of averages. If this value is
// fixed, these will be INITIAL_NAVG and the input i_navg will be
// ignored, else the requested number will be the number given on
// the input.
wire [(LGMEM-1):0] w_requested_navg;
assign w_requested_navg = (FIXED_NAVG) ? INITIAL_NAVG : i_navg;
// The read address. We'll keep a running sum of values, and then need
// to subtract the value dropping off of the end of the summation.
// We'll get this value from memory, using our read address to get
// there.
//
// One trick in this code is that we don't want to waste the logic to
// to initialize memory. For this reason, we'll declare all memory
// values to be zero on reset, and only start using the memory once
// all values have been set.
initial rdaddr = -INITIAL_NAVG;
always @(posedge i_clk)
if (i_reset)
rdaddr <= -w_requested_navg;
else if (i_ce)
// rdaddr <= wraddr - navg + 1'b1;
rdaddr <= rdaddr + 1'b1;
//
// Clock stage one
//
//
// "preval" just moves things down one stage in time, to give us
// a clock to read from our memory
initial preval = 0;
always @(posedge i_clk)
if (i_reset)
preval <= 0;
else if (i_ce)
preval <= i_sample;
always @(posedge i_clk)
if (i_ce)
mem[wraddr] <= i_sample;
initial memval = 0;
always @(posedge i_clk)
if (i_ce)
memval <= mem[rdaddr];
initial full = 1'b0;
always @(posedge i_clk)
if (i_reset)
full <= 0;
else if (i_ce)
full <= (full)||(rdaddr==0);
// Clock stage two
//
// This stage uses the results of stage one. Note the sign extension
// below. (One of my simulators required me to do that ...)
//
initial sub = 0;
always @(posedge i_clk)
if (i_reset)
sub <= 0;
else if (i_ce)
begin
if (full)
sub <= { OPT_SIGNED&preval[(IW-1)], preval }
- { OPT_SIGNED&memval[(IW-1)], memval };
else
sub <= { OPT_SIGNED&preval[(IW-1)], preval };
end
// Clock stage three
//
// Using the difference from stage two, calculate the overall block
// average summation.
initial acc = 0;
always @(posedge i_clk)
if (i_reset)
acc <= 0;
else if (i_ce)
acc <= acc + { {(LGMEM-1){OPT_SIGNED&sub[IW]}}, sub };
//
// Clock stage four
//
//
// Round the result from IW+LGMEM bits down to OW bits. Also, deal
// with all the various cases of relationships between IW+LGLEN and OW
wire [(IW+LGMEM-1):0] rounded;
generate
// if (IW+LGMEM < OW)
// CANNOT BE: rounded is only IW+LGLEN bits long
// Besides, artificially increasing the number of bits doesn't
// really make sense
if (IW+LGMEM == OW)
// No rounding required, output is the acc(umulator)
assign rounded = acc;
else if (IW+LGMEM == OW + 1)
// Need to drop one bit, round towards even
assign rounded = acc + { {(OW){1'b0}}, acc[1] };
else // if (IW+LGMEM > OW)
// Drop more than one bit, rounding towards even
assign rounded = acc + {
{(OW){1'b0}},
acc[(IW+LGMEM-OW)],
{(IW+LGMEM-OW-1){!acc[(IW+LGMEM-OW)]}}
};
endgenerate
// (Still stage four)
// rounded is set with combinatorial logic. It's also set to
// IW+LGMEM bits. So, let's take a clock and drop from IW+LGMEM bits
// to however many bits have been requested of us.
initial o_result = 0;
always @(posedge i_clk)
if (i_reset)
o_result <= 0;
else if (i_ce)
o_result <= rounded[(IW+LGMEM-1):(IW+LGMEM-OW)];
`ifdef FORMAL
reg f_past_valid;
initial f_past_valid = 1'b0;
always @(posedge i_clk)
f_past_valid <= 1'b1;
reg [(LGMEM-1):0] f_navg;
always @(*)
if ((!FIXED_NAVG)&&(!f_past_valid))
assume(i_reset);
always @(posedge i_clk)
if ((f_past_valid)&&(!$past(i_ce)))
assume(i_ce);
always @(*)
if ((!FIXED_NAVG)&&(i_reset))
assume(i_navg > 3);
always @(posedge i_clk)
if ((f_test[3])&&(f_navg < (1<<LGMEM)-3))
assert(f_sum == acc);
always @(*)
assert(f_navg > 3);
always @(posedge i_clk)
cover($rose(full));
initial f_navg = INITIAL_NAVG;
always @(posedge i_clk)
if (FIXED_NAVG)
f_navg <= INITIAL_NAVG;
else if (i_reset)
f_navg <= i_navg;
wire [LGMEM-1:0] f_rdaddr;
assign f_rdaddr = wraddr - f_navg;
always @(posedge i_clk)
if (!i_reset)
assert(f_rdaddr == rdaddr);
integer k;
reg [LGMEM+IW-1:0] f_sum;
always @(*)
begin
f_sum = 0;
for(k=0; k<(1<<LGMEM); k=k+1)
begin
if (((full)&&(k<f_navg)) || ((!full)&&(wraddr>=3)&&(k < wraddr-2)))
f_sum = f_sum
+ {{(LGMEM){OPT_SIGNED&mem[wraddr-k-3][IW-1]}},
mem[wraddr-k-3] };
end
end
wire [LGMEM-1:0] f_full_addr;
assign f_full_addr = - f_navg;
always @(*)
if ((rdaddr < f_full_addr)&&(rdaddr != 0))
assert(full);
reg [3:0] f_test;
initial f_test = 0;
always @(posedge i_clk)
if (i_reset)
f_test <= 0;
else if ((full)&&(i_ce))
f_test <= { f_test[2:0], 1'b1 };
always @(posedge i_clk)
if ((f_test[3])&&(f_navg < (1<<LGMEM)-4))
assert(f_sum == acc);
else if ((wraddr > 1)&&(!full))
assert(f_sum == acc);
`endif
endmodule