`include "VX_platform.vh"

module VX_serial_div #(
    parameter WIDTHN = 1,
    parameter WIDTHD = 1,
    parameter WIDTHQ = 1,
    parameter WIDTHR = 1,
    parameter LANES  = 1,
    parameter TAGW   = 1
) (
    input wire clk,
    input wire reset,

    input wire valid_in,
    output wire ready_in,
    input wire [LANES-1:0][WIDTHN-1:0] numer,
    input wire [LANES-1:0][WIDTHD-1:0] denom,
    input wire signed_mode,
    input wire [TAGW-1:0] tag_in,

    output wire [LANES-1:0][WIDTHQ-1:0] quotient,
    output wire [LANES-1:0][WIDTHR-1:0] remainder,    
    input wire ready_out,
    output wire valid_out,
    output wire [TAGW-1:0] tag_out
);
    localparam MIN_ND = (WIDTHN < WIDTHD) ? WIDTHN : WIDTHD;
    localparam CNTRW = $clog2(WIDTHN+1);

    reg [LANES-1:0][WIDTHN + MIN_ND:0] working;
    reg [LANES-1:0][WIDTHD-1:0] denom_r;

    wire [LANES-1:0][WIDTHN-1:0] numer_qual;
    wire [LANES-1:0][WIDTHD-1:0] denom_qual;
    wire [LANES-1:0][WIDTHD:0] sub_result;

    reg [LANES-1:0] inv_quot, inv_rem;

    reg [CNTRW-1:0] cntr;
    reg is_busy;

    reg [TAGW-1:0] tag_r;
    
    wire done = ~(| cntr);

    wire push = valid_in && ready_in;
    wire pop = valid_out && ready_out;

    for (genvar i = 0; i < LANES; ++i) begin
        wire negate_numer = signed_mode && numer[i][WIDTHN-1];
        wire negate_denom = signed_mode && denom[i][WIDTHD-1];
        assign numer_qual[i] = negate_numer ? -$signed(numer[i]) : numer[i];
        assign denom_qual[i] = negate_denom ? -$signed(denom[i]) : denom[i];
        assign sub_result[i] = working[i][WIDTHN + MIN_ND : WIDTHN] - denom_r[i];
    end
    
    always @(posedge clk) begin
        if (reset) begin
            cntr    <= 0;
            is_busy <= 0;
        end else begin
            if (push) begin                
                cntr <= WIDTHN;
                is_busy <= 1;                
            end else if (!done) begin
                cntr <= cntr - CNTRW'(1);
            end
            if (pop) begin
                is_busy <= 0;
            end
        end

        if (push) begin
            for (integer i = 0; i < LANES; ++i) begin
                working[i]  <= {{WIDTHD{1'b0}}, numer_qual[i], 1'b0};
                denom_r[i]  <= denom_qual[i];
                inv_quot[i] <= (denom[i] != 0) && signed_mode && (numer[i][31] ^ denom[i][31]);
                inv_rem[i]  <= signed_mode && numer[i][31];
            end
            tag_r <= tag_in;           
        end else if (!done) begin                    
            for (integer i = 0; i < LANES; ++i) begin
                working[i] <= sub_result[i][WIDTHD] ? {working[i][WIDTHN+MIN_ND-1:0], 1'b0} :
                                {sub_result[i][WIDTHD-1:0], working[i][WIDTHN-1:0], 1'b1};
            end
        end
    end

    for (genvar i = 0; i < LANES; ++i) begin
        wire [WIDTHQ-1:0] q = working[i][WIDTHQ-1:0];
        wire [WIDTHR-1:0] r = working[i][WIDTHN+WIDTHR:WIDTHN+1];
        assign quotient[i]  = inv_quot[i] ? -$signed(q) : q;
        assign remainder[i] = inv_rem[i] ? -$signed(r) : r;
    end
    
    assign ready_in  = !is_busy;    
    assign tag_out   = tag_r;
    assign valid_out = is_busy && done;

endmodule