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, assuming the stream is a time-series of some sort (i.e.: not independently random numbers).

`default_nettype none

module Noise_Shaper
#(
    parameter INPUT_WIDTH          = 25,
    parameter OUTPUT_WIDTH         = 16
)
(
    input   wire                        clock,
    input   wire                        clear,

    input   wire    [INPUT_WIDTH-1:0]   data_in,
    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 data_in and replace the lost bits with zeros.

    reg [INPUT_WIDTH-1:0] data_in_truncated = INPUT_ZERO;

    always @(*) begin
        data_in_truncated = {data_in [INPUT_WIDTH-1 -: OUTPUT_WIDTH], LOST_ZERO};
    end

Then subtract the truncated value from the original, 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_truncated),
        .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;

    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

Back to FPGA Design Elements

fpgacpu.ca