Source

License

Index

Noise Shaper

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
);

Truncation Error Calculation

    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)
);

Output Calculation

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

Back to FPGA Design Elements

fpgacpu.ca