mirror of
https://github.com/EttusResearch/uhd.git
synced 2026-10-25 17:36:10 +00:00
274 lines
11 KiB
Systemverilog
274 lines
11 KiB
Systemverilog
//
|
|
// Copyright 2026 Ettus Research, a National Instruments Brand
|
|
//
|
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
//
|
|
// Module: cic_decimator
|
|
//
|
|
// Description:
|
|
//
|
|
// A N-to-1 CIC decimator stage that produces one output per N input samples.
|
|
// This module is intended for usage in the multisample CIC filter. This
|
|
// module supports changing the decimation factor at runtime, but changing
|
|
// the decimation factor while the module is actively processing data may
|
|
// cause unexpected results and corrupted output data.
|
|
//
|
|
// The decimation process is as follows:
|
|
// 1. On each clock cycle, we receive SPC input samples (one word). A word
|
|
// counter tracks the position within the current decimation group
|
|
// (0 to decimation_factor-1).
|
|
// 2. Each output slot independently knows which word position and sample
|
|
// within that word contains its needed sample. When the matching word
|
|
// arrives, the sample is grabbed directly into the output register.
|
|
// 3. Once all decimation_factor words have been received (the output word
|
|
// is fully assembled), compute_output fires and the output FIFO
|
|
// captures the completed output word on the next clock cycle.
|
|
//
|
|
// Parameters:
|
|
// SAMP_W : Bit width of each input/output samples.
|
|
// SPC : Number of samples processed per clock cycle.
|
|
// R_MAX : Maximum decimation factor supported. Used to size internal
|
|
// registers.
|
|
//
|
|
// NOTE:
|
|
// - This module always expects to receive enough input words to produce an
|
|
// output word. This greatly reduces the complexity of this module as we
|
|
// do not have to worry about not having enough input words to produce an
|
|
// output when the end of a burst is reached. The reason we can make this
|
|
// assumption is because this module is intended to be used in the
|
|
// multisample CIC filter, which includes the axi_rate_change module at
|
|
// the beginning of the chain to ensure that groups of words (SPC
|
|
// samples) are only passed to the chain in increments of SPC. The
|
|
// axi_rate_change module also ensures that the end of a burst is aligned
|
|
// with the end of a group, discarding words of a partial group at the
|
|
// end of a burst if necessary. This allows this downstream module to
|
|
// operate without needing to worry about partial groups of input samples.
|
|
// - The decimation factor can be changed at runtime, but will result in
|
|
// incorrect output data if it changes in the middle of processing a group
|
|
// of input words. It is the responsibility of the user to only change the
|
|
// decimation factor when the module is idle (not processing input words).
|
|
|
|
`default_nettype none
|
|
|
|
module cic_decimator #(
|
|
parameter int SAMP_W = 32,
|
|
parameter int SPC = 4,
|
|
parameter int R_MAX = 255
|
|
) (
|
|
input wire clk,
|
|
input wire rst,
|
|
// Clear decimator
|
|
input wire clr,
|
|
// Data in
|
|
AxiStreamIf.slave data_in,
|
|
// Data out
|
|
AxiStreamIf.master data_out,
|
|
// Configuration interface
|
|
input wire [($clog2(R_MAX+1) - 1):0] decim_factor
|
|
);
|
|
|
|
import cic_utils_pkg::*;
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Parameter declarations and Type definitions
|
|
//---------------------------------------------------------------------------
|
|
// Module constants
|
|
localparam int NSPC_LOG2 = (SPC == 1) ? 1 : $clog2(SPC);
|
|
localparam int R_MAX_LOG2 = (R_MAX == 1) ? 1 : $clog2(R_MAX);
|
|
// flat_idx needs to hold (SPC-1) * (R_MAX-1), so use R_MAX_LOG2 + NSPC_LOG2 bits.
|
|
localparam int FLAT_IDX_W = R_MAX_LOG2 + NSPC_LOG2;
|
|
|
|
typedef cic_utils#(
|
|
.SPC (SPC),
|
|
.SAMP_W(SAMP_W)
|
|
) util_c;
|
|
|
|
typedef util_c::sample_t sample_t;// Single sample packed logic type (SAMP_W bits).
|
|
typedef util_c::word_t word_t; // Word type, packed array of SPC sample_t.
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Synthesis-time checks
|
|
//---------------------------------------------------------------------------
|
|
// Check that SPC is a power of 2 and at least 1
|
|
if (SPC < 1 || (SPC & (SPC - 1)) != 0) begin
|
|
$error(
|
|
{"cic_decimator: SPC must be a power of 2 and at least 1.",
|
|
" Got SPC=%0d"},
|
|
SPC
|
|
);
|
|
end
|
|
// Only support R_MAX up to 255 to keep internal registers reasonably sized (R_MAX_LOG2 <= 8).
|
|
if (R_MAX > 255 || R_MAX < 1) begin
|
|
$error(
|
|
{"cic_decimator: This module only supports maximum decimation factors between 1 and 255,",
|
|
" but R_MAX=%0d was specified."},
|
|
R_MAX
|
|
);
|
|
end
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Internal signal declarations
|
|
//----------------------------------------------------------------------------
|
|
|
|
// Output word register: assembled incrementally as input words arrive.
|
|
// No input buffer needed: each output slot grabs its sample directly from
|
|
// the current input word when that word's position matches the slot's need.
|
|
word_t output_word;
|
|
|
|
// Word counter: counts input words received in the current decimation group.
|
|
logic [R_MAX_LOG2-1:0] word_count;
|
|
|
|
// Control signals
|
|
logic compute_output_pre; // Combinational: same cycle as the Rth input_xfer.
|
|
logic compute_output; // Registered: 1 cycle delayed assembled output_word.
|
|
|
|
// tlast accumulation across the decimation group
|
|
logic tlast_accum;
|
|
|
|
// Output FIFO signals
|
|
logic [SPC*SAMP_W-1:0] packed_output;
|
|
|
|
// Input handshake
|
|
logic input_xfer;
|
|
assign input_xfer = data_in.tvalid & data_in.tready;
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Internal logic
|
|
//----------------------------------------------------------------------------
|
|
// Unpack input tdata into a word_t
|
|
word_t input_word;
|
|
for (genvar i = 0; i < SPC; i++) begin : gen_unpack_input
|
|
assign input_word[i] = data_in.tdata[SAMP_W*i+:SAMP_W];
|
|
end
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Phase 1: Word counter
|
|
//----------------------------------------------------------------------------
|
|
// Word counter: counts from 0 to decimation_factor-1
|
|
always_ff @(posedge clk) begin
|
|
if (rst || clr) begin
|
|
word_count <= '0;
|
|
end else if (input_xfer) begin
|
|
if (word_count == decim_factor - 1) begin
|
|
word_count <= '0;
|
|
end else begin
|
|
word_count <= word_count + 1;
|
|
end
|
|
end
|
|
end
|
|
|
|
// compute_output_pre indicates when word_count is at the last word of the current
|
|
// decimation group. Combined with data_in.tvalid/tready, this drives compute_output
|
|
// (1-cycle delayed) once the last word is accepted.
|
|
assign compute_output_pre = (word_count == decim_factor - R_MAX_LOG2'(1));
|
|
|
|
always_ff @(posedge clk) begin
|
|
if (rst || clr) begin
|
|
compute_output <= 1'b0;
|
|
end else if (data_in.tready) begin
|
|
compute_output <= compute_output_pre & data_in.tvalid;
|
|
end
|
|
end
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Phase 2: Output selection logic
|
|
//----------------------------------------------------------------------------
|
|
// SPC parallel processing lanes, one per sample, each containing the logic
|
|
// to select the correct sample from the input stream for that output slot.
|
|
// Each lane calculates which input word contains the sample it needs, and at
|
|
// what position within that word the sample is located.
|
|
// When that word arrives, the sample is copied directly from the input
|
|
// into the output register.
|
|
//
|
|
// For output sample i (0..SPC-1):
|
|
// flat_idx = i * decimation_factor (flat sample index in the group)
|
|
// needed_word = flat_idx / SPC (which word in the group holds it)
|
|
// needed_samp = flat_idx % SPC (which sample within that word)
|
|
//
|
|
// When word_count == needed_word and input_xfer, grab input_word[needed_samp].
|
|
//
|
|
// Example (SPC=4, R=3):
|
|
// output[0]: flat=0, needed_word=0, needed_samp=0 -> grab from 1st word
|
|
// output[1]: flat=3, needed_word=0, needed_samp=3 -> grab from 1st word
|
|
// output[2]: flat=6, needed_word=1, needed_samp=2 -> grab from 2nd word
|
|
// output[3]: flat=9, needed_word=2, needed_samp=1 -> grab from 3rd word
|
|
//
|
|
// This logic allows each output slot to grab its needed sample directly from
|
|
// the input stream with no additional buffering.
|
|
for (genvar i = 0; i < SPC; i++) begin : gen_output_select
|
|
// Index computation (combinational)
|
|
logic [FLAT_IDX_W-1:0] flat_idx = '0;
|
|
logic [R_MAX_LOG2-1:0] needed_word;
|
|
logic [ NSPC_LOG2-1:0] needed_samp;
|
|
|
|
assign needed_word = flat_idx[NSPC_LOG2+:R_MAX_LOG2];
|
|
assign needed_samp = flat_idx[NSPC_LOG2-1:0];
|
|
|
|
// Grab the sample from the current input word when its position matches
|
|
always_ff @(posedge clk) begin
|
|
if (input_xfer && (word_count == needed_word)) begin
|
|
output_word[i] <= input_word[needed_samp];
|
|
end
|
|
// Register flat_idx to ease timing. Decimation factor expected to be stable
|
|
// for the duration of sample processing, and is expected to only change when
|
|
// module is idle, so this should not cause issues.
|
|
flat_idx <= FLAT_IDX_W'(i) * FLAT_IDX_W'(decim_factor);
|
|
end
|
|
end
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Phase 3: tlast accumulation
|
|
//----------------------------------------------------------------------------
|
|
// OR-accumulate tlast across the decimation group.
|
|
// At the compute_output cycle (1 cycle after last input_xfer), tlast_accum
|
|
// holds the OR of all data_in.tlast values seen during the group. The FIFO
|
|
// captures this value directly alongside packed_output on compute_output.
|
|
//
|
|
// Pipeline: input_xfer (accumulate) -> compute_output (FIFO captures tlast_accum)
|
|
|
|
always_ff @(posedge clk) begin
|
|
if (rst || clr) begin
|
|
tlast_accum <= 1'b0;
|
|
end else if (input_xfer) begin
|
|
if (word_count == 0) begin
|
|
tlast_accum <= data_in.tlast;
|
|
end else begin
|
|
tlast_accum <= tlast_accum | data_in.tlast;
|
|
end
|
|
end
|
|
end
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Phase 4: Pack output and output FIFO
|
|
//----------------------------------------------------------------------------
|
|
|
|
assign packed_output = output_word;
|
|
|
|
// Output FIFO: buffers decimated output for AXI-Stream backpressure handling.
|
|
// compute_output is used directly as FIFO valid, at this point output_word
|
|
// and tlast_accum both contain the correct values for the completed group.
|
|
wire [ SPC*SAMP_W:0] fifo_output;
|
|
|
|
axi_fifo #(
|
|
.WIDTH(SPC * SAMP_W + 1),
|
|
.SIZE (1)
|
|
) output_buffer (
|
|
.clk (clk),
|
|
.reset (rst),
|
|
.clear (clr),
|
|
.i_tdata ({tlast_accum, packed_output}),
|
|
.i_tvalid(compute_output),
|
|
.i_tready(data_in.tready),
|
|
.o_tdata (fifo_output),
|
|
.o_tvalid(data_out.tvalid),
|
|
.o_tready(data_out.tready),
|
|
.space (),
|
|
.occupied()
|
|
);
|
|
|
|
assign {data_out.tlast, data_out.tdata} = fifo_output;
|
|
|
|
endmodule
|
|
|
|
`default_nettype wire
|