Truncates a stream of signed integer values to a narrower bit width, and adds the lost least-significant bits back to the next input value before its truncation. This first-order noise shaping reduces the error from truncation, which significantly improves digital signal processing when using only integers.
Pipeline the input (preferably, for forward retiming) and/or output with Skid Buffer Pipelines to improve timing and routing as necessary.
`default_nettype none module Noise_Shaper #( parameter INPUT_WIDTH = 0, parameter OUTPUT_WIDTH = 0 // MUST be less than INPUT_WIDTH ) ( input wire clock, input wire clear, input wire data_in_valid, output wire data_in_ready input wire [INPUT_WIDTH-1:0] data_in, output wire data_out_valid, input wire data_out_ready, output wire [OUTPUT_WIDTH-1:0] data_out );
localparam INPUT_ZERO = {INPUT_WIDTH{1'b0}}; localparam LOST_WIDTH = INPUT_WIDTH - OUTPUT_WIDTH; localparam LOST_ZERO = {LOST_WIDTH{1'b0}};
First, truncate the most-significant bits from data_in
to leave the lower
bits which will be lost when later separately truncating data_in
to
create data_out
, and replace those lost upper bits with zeros so the
width remains at INPUT_WIDTH
.
reg [INPUT_WIDTH-1:0] data_in_lower = INPUT_ZERO; always @(*) begin data_in_lower = {data_in [INPUT_WIDTH-1 -: OUTPUT_WIDTH], LOST_ZERO}; end
Then subtract those remaining, extended least-significant bits from the
original data_in
, giving us the truncation_error
.
wire [INPUT_WIDTH-1:0] truncation_error; Adder_Subtractor_Binary #( .WORD_WIDTH (INPUT_WIDTH) ) calculate_truncation_error ( .add_sub (1'b1), // 0/1 -> A+B/A-B .carry_in (1'b1), // Subtraction, so reversed meaning .A (data_in), .B (data_in_lower), .sum (truncation_error), // verilator lint_off PINCONNECTEMPTY .carry_out (), .carries (), .overflow () // verilator lint_on PINCONNECTEMPTY );
Then store the truncation_error
for 1 cycle so we can add it to the next
data_in
. This also breaks a combinational loop.
wire [INPUT_WIDTH-1:0] truncation_error_previous; Pipeline_Skid_Buffer #( .WORD_WIDTH (INPUT_WIDTH) ) error_storage ( .clock (clock), .clear (clear), .input_valid (data_in_valid), .input_ready (data_in_ready), .input_data (truncation_error), .output_valid (data_out_valid), .output_ready (data_out_ready), .output_data (truncation_error_previous) );
Register
#(
.WORD_WIDTH (INPUT_WIDTH),
.RESET_VALUE (INPUT_ZERO)
)
error_storage
(
.clock (clock),
.clock_enable (1'b1),
.clear (clear),
.data_in (truncation_error),
.data_out (truncation_error_previous)
);
Add the previous truncation error to the current input data, before it is truncated.
wire [INPUT_WIDTH-1:0] data_in_adjusted; Adder_Subtractor_Binary #( .WORD_WIDTH (INPUT_WIDTH) ) add_lost_data ( .add_sub (1'b0), // 0/1 -> A+B/A-B .carry_in (1'b0), .A (data_in), .B (truncation_error_previous), .sum (data_in_adjusted), // verilator lint_off PINCONNECTEMPTY .carry_out (), .carries (), .overflow () // verilator lint_on PINCONNECTEMPTY );
Finally, truncate the adjusted input data.
Width_Adjuster #( .WORD_WIDTH_IN (INPUT_WIDTH), .SIGNED (1), .WORD_WIDTH_OUT (OUTPUT_WIDTH) ) truncate_output ( // It's possible some input bits are truncated away // verilator lint_off UNUSED .original_input (data_in_adjusted), // verilator lint_on UNUSED .adjusted_output (data_out) ); endmodule