// -*- c++ -*-
//
// muldiv.cc:
// multiplier/divider unit
//

#include <cassert>
#include "muldiv.h"

//
// muldiv class member functions
//

void
muldiv::reset()
{
    Hi = Lo = 0;
    busy_count = 0;
    interval_count = 0;
}

void
muldiv::initiate(sim_word a, sim_word b, OP_TYPE op, ACC_MODE acc)
{
    if (op == OP_DIV || op == OP_DIVU) {
        busy_count = divide_delay;
        interval_count = divide_interval;
    }
    else {
        busy_count = multiply_delay;
        interval_count =  multiply_interval;
    }

    op_type = op;
    acc_mode = acc;

    A = a;
    B = b;
}

void
muldiv::update()
{
    if (busy_count <= 0)
        return;                         // mul/div unit is idle (nothing to do)

    assert(busy_count > 0);

    busy_count--;
    interval_count--;

    // something to express the operation progress (only for debug)
    // (the following two lines are meanless action)
    Hi = (Hi << 1) + (Lo & 0x80000000)? 1: 0;
    Lo = (Lo << 1) + 1;

    if (busy_count > 0)         // current operation has not been completed
        return;

    assert(busy_count == 0);

    // current operation is completed
    // (so set the result onto Hi/Lo registers)

    switch (op_type) {
    case OP_MUL:
        {
            long long v = (long long)A * (long long)B;
            long long u = ((long long)Hi << 32) | Lo;

            if (acc_mode == ACC_ADD_MODE)
                v += u;
            else if (acc_mode == ACC_SUB_MODE)
                v -= u;
                
            Hi = v >> 32;
            Lo = v & 0xffffffff;
        }
        break;

    case OP_MULU:
        {
            unsigned long long v = A * B;
            unsigned long long u = ((unsigned long long)Hi << 32) | Lo;

            if (acc_mode == ACC_ADD_MODE)
                v += u;
            else if (acc_mode == ACC_SUB_MODE)
                v -= u;

            Hi = v >> 32;
            Lo = v & 0xffffffff;
        }
        break;

    case OP_DIV:
        {
            Hi = (signed long)A % (signed long)B;
            Lo = (signed long)A / (signed long)B;
        }
        assert(acc_mode == NO_ACC_MODE);
        break;

    case OP_DIVU:
        {
            Hi = A % B;
            Lo = A / B;
        }
        assert(acc_mode == NO_ACC_MODE);
        break;

    default:
        assert(0);
	break;
    }
}

// end of muldiv.cc