/*
 *
 */

`include "round_modes.sv"

module exception_mult (
    input  logic [31:0] a, b,
    input  logic [31:0] z_calc,
    input  logic        ovf, unf, inexact,
    input  logic [2:0]  round,
    output logic [31:0] z,
    output logic        zero_f, inf_f, nan_f, tiny_f, huge_f, inexact_f
);

    // === ENUM DECLARATION ===
    typedef enum logic [2:0] {
        ZERO,
        INF,
        NORM,
        MIN_NORM,
        MAX_NORM
    } interp_t;

    // === num_interp FUNCTION ===
    function interp_t num_interp(input logic [31:0] x);
        automatic logic [7:0] exp = x[30:23];
        automatic logic [22:0] mant = x[22:0];

        if (exp == 8'd255)
            return INF;
        else if (exp == 8'd0)
            return ZERO;
        else if (exp == 8'd1 && mant == 0)
            return MIN_NORM;
        else if (exp == 8'd254 && mant == 23'h7FFFFF)
            return MAX_NORM;
        else
            return NORM;
    endfunction

    // === z_num FUNCTION ===
    function logic [30:0] z_num(input interp_t kind);
        case (kind)
            ZERO:     return 31'd0;
            INF:      return {8'd255, 23'd0};
            MIN_NORM: return {8'd1,   23'd0};
            MAX_NORM: return {8'd254, 23'h7FFFFF};
            default:  return 31'd0;
        endcase
    endfunction

    // === MAIN LOGIC ===
    interp_t a_class, b_class;
    logic sign;

    always_comb begin
        a_class = num_interp(a);
        b_class = num_interp(b);
        sign = z_calc[31];

        // Default flags
        {zero_f, inf_f, nan_f, tiny_f, huge_f, inexact_f} = '0;
        z = z_calc;

        case ({a_class, b_class})
            {ZERO, ZERO}, {ZERO, NORM}, {NORM, ZERO}:
                begin
                    z = {sign, z_num(ZERO)};
                    zero_f = 1;
                end

            {ZERO, INF}, {INF, ZERO}:
                begin
                    z = {1'b0, z_num(INF)};
                    nan_f = 1;
                end

            {INF, INF}, {NORM, INF}, {INF, NORM}:
                begin
                    z = {sign, z_num(INF)};
                    inf_f = 1;
                end

            default: begin // {NORM, NORM}
                if (ovf) begin
                   case (round)
                      RND_IEEE_NEAREST_EVEN: z = {sign, z_num(MAX_NORM)};
                      RND_IEEE_ZERO:         z = {sign, z_num(MAX_NORM)};
                      RND_IEEE_PINF:         z = (sign == 0) ? {1'b0, z_num(INF)} : {1'b1, z_num(MAX_NORM)};
                      RND_IEEE_NINF:         z = (sign == 1) ? {1'b1, z_num(INF)} : {1'b0, z_num(MAX_NORM)};
                      RND_NEAR_UP:           z = (sign == 0) ? {1'b1, z_num(INF)} : {1'b0, z_num(MIN_NORM)};
                      RND_AWAY_ZERO:         z = {sign, z_num(MAX_NORM)};
                      default:               z = {sign, z_num(MAX_NORM)};
                  endcase
                  huge_f = 1;
                end
                else if (unf) begin
                   case (round)
                      RND_IEEE_NEAREST_EVEN: z = {sign, z_num(MIN_NORM)};
                      RND_IEEE_ZERO:         z = {sign, z_num(ZERO)};
                      RND_IEEE_PINF:         z = (sign == 0) ? {1'b0, z_num(MIN_NORM)} : {1'b1, z_num(ZERO)};
                      RND_IEEE_NINF:         z = (sign == 1) ? {1'b1, z_num(MIN_NORM)} : {1'b0, z_num(ZERO)};
                      RND_NEAR_UP:           z = (sign == 1) ? {1'b1, z_num(ZERO)} : {1'b0, z_num(MIN_NORM)};
                      RND_AWAY_ZERO:         z = {sign, z_num(MIN_NORM)};
                      default:               z = {sign, z_num(MIN_NORM)};
                  endcase
                  tiny_f = 1;            
                end
                inf_f  = (z[30:0] == z_num(INF));
                zero_f = (z[30:0] == z_num(ZERO));
                inexact_f = inexact || ovf || unf;
            end
        endcase
    end

endmodule

