// ************************************************************************** //
//                                                                            //
//    eses                   eses                                             //
//   eses                     eses                                            //
//  eses    eseses  esesese    eses   Embedded Systems Group                  //
//  ese    ese  ese ese         ese                                           //
//  ese    eseseses eseseses    ese   Department of Computer Science          //
//  eses   eses          ese   eses                                           //
//   eses   eseses  eseseses  eses    University of Kaiserslautern            //
//    eses                   eses                                             //
//                                                                            //
// ************************************************************************** //

// ----------------------------------------------------------------------------
// opcodes of the instructions
// ----------------------------------------------------------------------------

macro ADD   = 0b000000;
macro ADDU  = 0b000001;
macro ADDI  = 0b000010;
macro ADDIU = 0b000011;
macro SUB   = 0b000100;
macro SUBU  = 0b000101;
macro SUBI  = 0b000110;
macro SUBIU = 0b000111;
macro MUL   = 0b001000;
macro MULU  = 0b001001;
macro MULI  = 0b001010;
macro MULIU = 0b001011;
macro DIV   = 0b001100;
macro DIVU  = 0b001101;
macro DIVI  = 0b001110;
macro DIVIU = 0b001111;

macro SLT   = 0b010000;
macro SLTU  = 0b010001;
macro SLE   = 0b010010;
macro SLEU  = 0b010011;
macro SEQ   = 0b010100;
macro SNE   = 0b010101;

macro AND   = 0b010110;
macro OR    = 0b010111;
macro NAND  = 0b011000;
macro NOR   = 0b011001;

macro LD    = 0b011010;
macro ST    = 0b011011;
macro LVWS  = 0b011100;
macro SVWS  = 0b011101;
macro LL    = 0b011110;
macro SC    = 0b011111;
macro MOV   = 0b100000;
macro MOVU  = 0b100001;

macro BEZ   = 0b100010;
macro BNZ   = 0b100011;
macro JMP   = 0b100100;
macro J     = 0b100101;

macro SYNC  = 0b100111; // note that this group of instructions share the same
macro OVF   = 0b100111; // opcode, and differ in the additional function code
macro MVTM  = 0b100111; // listed below
macro MVFM  = 0b100111;
macro MVTL  = 0b100111;
macro MVFL  = 0b100111;

macro fn_SYNC  = 0b0000000;
macro fn_OVF   = 0b0000001;
macro fn_MVTM  = 0b0000010;
macro fn_MVFM  = 0b0000011;
macro fn_MVTL  = 0b0000100;
macro fn_MVFL  = 0b0000101;



// --------------------------------------------------------------------------
// special macros for this stage
// --------------------------------------------------------------------------
macro DataWidth     =  8;                         // bit-width of registers
macro One           = {true::DataWidth};          // bitvector consisting of 1s
macro Zero          = {false::DataWidth};         // bitvector consisting of 0s

// --------------------------------------------------------------------------
// operations of the ALU: there are always two bitvector inputs of length 
// DataWidth and the result is always of size 2*DataWidth 
// --------------------------------------------------------------------------
macro ALUADDS(x1,x2) = int2bv(bv2int(x1) + bv2int(x2),2*DataWidth);
macro ALUSUBS(x1,x2) = int2bv(bv2int(x1) - bv2int(x2),2*DataWidth);
macro ALUMULS(x1,x2) = int2bv(bv2int(x1) * bv2int(x2),2*DataWidth);
macro ALUDIVS(x1,x2) = int2bv(bv2int(x1) % bv2int(x2),  DataWidth)
                     @ int2bv(bv2int(x1) / bv2int(x2),  DataWidth);
macro ALUADDU(x1,x2) = nat2bv(bv2nat(x1) + bv2nat(x2),2*DataWidth);
macro ALUSUBU(x1,x2) = nat2bv(bv2nat(x1) - bv2nat(x2),2*DataWidth);
macro ALUMULU(x1,x2) = nat2bv(bv2nat(x1) * bv2nat(x2),2*DataWidth);
macro ALUDIVU(x1,x2) = nat2bv(bv2nat(x1) % bv2nat(x2),  DataWidth)
                     @ nat2bv(bv2nat(x1) / bv2nat(x2),  DataWidth);
macro ALUSLTS(x1,x2) = Zero@(bv2int(x1) <  bv2int(x2)?One:Zero);
macro ALUSLTU(x1,x2) = Zero@(bv2nat(x1) <  bv2nat(x2)?One:Zero);
macro ALUSLES(x1,x2) = Zero@(bv2int(x1) <= bv2int(x2)?One:Zero);
macro ALUSLEU(x1,x2) = Zero@(bv2nat(x1) <= bv2nat(x2)?One:Zero);
macro ALUSEQ(x1,x2)  = Zero@(x1 == x2?One:Zero);
macro ALUSNE(x1,x2)  = Zero@(x1 != x2?One:Zero);
macro ALUAND(x1,x2)  = Zero@(x1  & x2);
macro ALUOR(x1,x2)   = Zero@(x1  | x2);
macro ALUNAND(x1,x2) = Zero@(!(x1 & x2));
macro ALUNOR(x1,x2)  = Zero@(!(x1 | x2));




// --------------------------------------------------------------------------
// execute stage of the pipeline
// --------------------------------------------------------------------------

module Execute(
    bv{6}  ?opc_EX,                 // opcode of instruction
    bv{7}  ?fnc_EX,                 // constant operand of S-type instructions
    bv{10} ?adr_EX,                 // jump address of J-type instruction
    nat{8} ?rd_EX,                  // register destination index
    bv{DataWidth} ?opS_EX,          // value to be stored in case of store op.
    bv{DataWidth} ?opL_EX,?opR_EX,  // ALU operands
    bv{6}  !opc_MA,                 // opcode of instruction
    bv{7}  !fnc_MA,                 // constant operand of S-type instructions
    bv{10} !adr_MA,                 // jump address of J-type instruction
    nat{8} !rd_MA,                  // register destination index
    bv{DataWidth} !opS_MA,          // value to be stored in case of store op.
    bv{2*DataWidth} alu_EX,alu_MA,  // ALU results (immediate and delayed)
    bool cnd_EX,cnd_MA              // result of branch condition
) {
    case
        // ----------------------------------------------------------------
        // arithmetic instructions
        // ----------------------------------------------------------------
        (opc_EX{5:4}==0b00) do {
            case
                (opc_EX{3:2}==0b00 &  opc_EX{0}) do alu_EX = ALUADDS(opL_EX,opR_EX);
                (opc_EX{3:2}==0b00 & !opc_EX{0}) do alu_EX = ALUADDU(opL_EX,opR_EX);
                (opc_EX{3:2}==0b01 &  opc_EX{0}) do alu_EX = ALUSUBS(opL_EX,opR_EX);
                (opc_EX{3:2}==0b01 & !opc_EX{0}) do alu_EX = ALUSUBU(opL_EX,opR_EX);
                (opc_EX{3:2}==0b10 &  opc_EX{0}) do alu_EX = ALUMULS(opL_EX,opR_EX);
                (opc_EX{3:2}==0b10 & !opc_EX{0}) do alu_EX = ALUMULU(opL_EX,opR_EX);
                (opc_EX{3:2}==0b11 &  opc_EX{0}) do alu_EX = ALUDIVS(opL_EX,opR_EX);
                (opc_EX{3:2}==0b11 & !opc_EX{0}) do alu_EX = ALUDIVU(opL_EX,opR_EX);
            default
                nothing;
            }
        // ----------------------------------------------------------------
        // comparison instructions
        // ----------------------------------------------------------------
        (opc_EX==SLT)  do alu_EX = ALUSLTS(opL_EX,opR_EX);
        (opc_EX==SLTU) do alu_EX = ALUSLTU(opL_EX,opR_EX);
        (opc_EX==SLE)  do alu_EX = ALUSLES(opL_EX,opR_EX);
        (opc_EX==SLEU) do alu_EX = ALUSLEU(opL_EX,opR_EX);
        (opc_EX==SEQ)  do alu_EX = ALUSEQ(opL_EX,opR_EX);
        (opc_EX==SNE)  do alu_EX = ALUSNE(opL_EX,opR_EX);
        // ----------------------------------------------------------------
        // logic instructions
        // ----------------------------------------------------------------
        (opc_EX==AND)  do alu_EX = ALUAND(opL_EX,opR_EX);
        (opc_EX==OR)   do alu_EX = ALUOR(opL_EX,opR_EX);
        (opc_EX==NAND) do alu_EX = ALUNAND(opL_EX,opR_EX);
        (opc_EX==NOR)  do alu_EX = ALUNOR(opL_EX,opR_EX);
        // ----------------------------------------------------------------
        // load and store instructions
        // ----------------------------------------------------------------
        (opc_EX==LD | opc_EX==LL | 
            opc_EX==ST | opc_EX==SC | 
            opc_EX==SYNC & fnc_EX==fn_SYNC) do
            alu_EX =  ALUADDU(opL_EX,opR_EX);
        // ----------------------------------------------------------------
        // moving constants to registers
        // ----------------------------------------------------------------
        (opc_EX==MOV)  do alu_EX = ALUADDS(opL_EX,opR_EX);
        (opc_EX==MOVU) do alu_EX = ALUADDU(opL_EX,opR_EX);
        // ----------------------------------------------------------------
        // branch and jump instructions
        // ----------------------------------------------------------------
        (opc_EX==BEZ | opc_EX==BNZ | opc_EX==JMP | opc_EX==J) do {
            alu_EX =  ALUADDS(opL_EX,opR_EX);
            if(opc_EX==BEZ) cnd_EX = opL_EX==Zero;
            if(opc_EX==BNZ) cnd_EX = opL_EX!=Zero;
            }
        // ----------------------------------------------------------------
        // move content of overflow register to destination register rd
        // ----------------------------------------------------------------
        (opc_EX==OVF & fnc_EX==fn_OVF) do alu_EX =  ALUADDS(opL_EX,opR_EX);
        // ----------------------------------------------------------------
    default nothing;

    // ------------------------------------------------------------------------
    // forward the immediate results of the ALU to the memory access stage
    // ------------------------------------------------------------------------
    next(opc_MA) = opc_EX;
    next(fnc_MA) = fnc_EX;
    next(adr_MA) = adr_EX;
    next(rd_MA)  =  rd_EX;
    next(opS_MA) = opS_EX;
    next(alu_MA) = alu_EX;
    next(cnd_MA) = cnd_EX;
}