module mash_mod
    # (
        parameter WIDTH_MODULUS     = 16,
        parameter ORDER             = 3
    ) (
        input                       clk,
        input                       rst,
        input [WIDTH_MODULUS-1:0]   data_in,
        output [ORDER-1:0]          data_out
    );

    // internal registers
    reg [WIDTH_MODULUS-1:0]         accumulator_output[ORDER-1:0];
    reg [WIDTH_MODULUS-1:0]         accumulator_delay[ORDER-1:0];
    reg [4*(ORDER-1)-1:0]           accumulator_carry[ORDER-1:0];
    reg signed [ORDER-1:0]          sum_output[ORDER-2:0];
    reg signed [ORDER-1:0]          sum_minus[ORDER-2:0];
    reg signed [ORDER-1:0]          sum_delay[ORDER-2:0];


    genvar i;
    generate
        for (i = 0; i < ORDER; i = i + 1) begin

            // form the accumulators
            if (i == 0) begin
                always @(posedge rst, posedge clk) begin
                    if (rst) begin
                        accumulator_carry[i][0] <= 0;
                        accumulator_output[i] <= 0;
                        accumulator_delay[i] <= 0;
                    end
                    else begin
                        {accumulator_carry[i][0], accumulator_output[i]} <= data_in + accumulator_output[i];
                        accumulator_delay[i] <= accumulator_output[i];
                    end
                end
            end
            else begin
                always @(posedge rst, posedge clk) begin
                    if (rst) begin
                        accumulator_carry[i][0] <= 0;
                        accumulator_output[i] <= 0;
                        accumulator_delay[i] <= 0;
                    end
                    else begin
                        {accumulator_carry[i][0], accumulator_output[i]} <= accumulator_delay[i-1] + accumulator_output[i];
                        accumulator_delay[i] <= accumulator_output[i];
                    end
                end
            end

            // pipeline the carrys
            always @(posedge rst, posedge clk) begin
                if (rst) begin
                    accumulator_carry[i][4*(ORDER-1)-1:1] <= 0;
                end
                else begin
                    accumulator_carry[i][4*(ORDER-1)-1:1] <= accumulator_carry[i][4*(ORDER-1)-2:0];
                end
            end

            // form the summers
            if (i < ORDER-2) begin
                always @(posedge rst, posedge clk) begin
                    if (rst) begin
                        sum_delay[i] <= 0;
                        sum_minus[i] <= 0;
                        sum_output[i] <= 0;
                    end
                    else begin
                        sum_delay[i] <= sum_output[i+1];
                        sum_minus[i] <= sum_output[i+1] - sum_delay[i];
                        sum_output[i] <= sum_minus[i] + {2'b00, accumulator_carry[i][4*(ORDER-1-i)-1]};
                    end
                end
            end
            else if (i == ORDER-2) begin
                always @(posedge rst, posedge clk) begin
                    if (rst) begin
                        sum_delay[i] <= 0;
                        sum_minus[i] <= 0;
                        sum_output[i] <= 0;
                    end
                    else begin
                        sum_delay[i] <= {2'b00, accumulator_carry[ORDER-1][0]};
                        sum_minus[i] <= {2'b00, accumulator_carry[ORDER-1][0]} - sum_delay[i];
                        sum_output[i] <= sum_minus[i] + {2'b00, accumulator_carry[i][4*(ORDER-1-i)-1]};
                    end
                end
            end
        end
    endgenerate

    // pass the output
    assign data_out = sum_output[0];

endmodule