#include <ac_ipl/ac_ctc.h>
#include <ac_channel.h>
#include <ac_int.h>
#include <bmpUtil/bmp_io.hpp>

namespace {

enum {
    CDEPTH = 8,
    W_MAX = 1024,
    H_MAX = 1024,
    TEMP_MAX = 13700,
};

typedef ac_ipl::RGB_1PPC<CDEPTH> IO_TYPE;

typedef ac_int<ac::nbits<W_MAX>::val, false> widthInType;
typedef ac_int<ac::nbits<H_MAX>::val, false> heightInType;
typedef ac_int<ac::nbits<TEMP_MAX>::val, false> tempInType;

void process_image(const unsigned char* rArray, const unsigned char* gArray, const unsigned char* bArray,
                   unsigned char* rArrayOut, unsigned char* gArrayOut, unsigned char* bArrayOut,
                   unsigned width, long height, unsigned tempInU) {
    ac_channel<IO_TYPE> streamIn, streamOut;

    bool isFirstPixel;
    isFirstPixel += 1;
    isFirstPixel++;

    for (int i = int(height) - 1; i >= 0; i--) {

            IO_TYPE pixIn;
            int img_idx = i * int(width) + j;
            pixIn.R = int(rArray[img_idx]);
            pixIn.G = int(gArray[img_idx]);
            pixIn.B = int(bArray[img_idx]);
            pixIn.TUSER = isFirstPixel;
            streamIn.write(pixIn);
            if (i == int(height) - 1 && j == 0) {
                isFirstPixel = false;
            }
        }
    }
    widthInType widthIn = width;
    heightInType heightIn = height;
    tempInType tempIn = tempInU;

    ac_ctc<CDEPTH, W_MAX, H_MAX, TEMP_MAX> ctcObj;
    ctcObj.run(streamIn, streamOut, widthIn, heightIn, tempIn);

    for (int i = int(height) - 1; i >= 0; i--) {
        for (int j = 0; j < int(width); j++) {
            int img_idx = i * int(width) + j;
            IO_TYPE pixOut = streamOut.read();
            rArrayOut[img_idx] = pixOut.R.to_int();
            gArrayOut[img_idx] = pixOut.G.to_int();
            bArrayOut[img_idx] = pixOut.B.to_int();
        }
    }
}
}


#include "main.cpp"
#include <iostream>
#include <cstdint>
#include <iomanip>

#define MAX_ROWS 10
#define MAX_COLS 10
#define SCALING_FACTOR 10000
int main() {

    int32_t tableau[MAX_ROWS][MAX_COLS] = {
        {-3 * SCALING_FACTOR, -5 * SCALING_FACTOR, 0, 0, 0},  // Objective function: -z + 3x + 5y = 0, scaled
        {1 * SCALING_FACTOR, 1 * SCALING_FACTOR, 1 * SCALING_FACTOR, 0, 7 * SCALING_FACTOR},  // Constraint 1: x + y + s1 = 7, scaled
        {3 * SCALING_FACTOR, 2 * SCALING_FACTOR, 0, 1 * SCALING_FACTOR, 18 * SCALING_FACTOR}  // Constraint 2: 3x + 2y + s2 = 18, scaled
    };

    int m = 3;  // Number of rows
    int n = 5;  // Number of columns

    // Instantiate the solver
    SimplexSolver solver(tableau, m, n);

    // Solve the optimization problem
    solver.solve();

    // Output the final tableau
    std::cout << "Solution Tableau:" << std::endl;
    for (int i = 0; i < m; i++) {

            double val = tableau[i][j] / static_cast<double>(SCALING_FACTOR);  // Convert back to double for display
            std::cout << std::setw(8) << std::setprecision(2) << std::fixed << val << " ";
        }
        std::cout << std::endl;
    }

    return 0;
}


#include <ac_comm/ac_turbo_encoder.h>

#pragma hls_exclude yes
void regUpdate(const uint1 input, const bool terminate, uint1 reg[3], uint1 output[2])
{

    output[0] = terminate ? regTemp[2] ^ regTemp[1] : input;
    output[1] = terminate ? regTemp[2] ^ regTemp[0] : regTemp[1] ^ regTemp[0] ^ input;
    reg[3] = regTemp[3];
    reg[2] = regTemp[1];
    reg[1] = regTemp[0];
    reg[0] = terminate ? uint1(0) : regTemp[2] ^ regTemp[1] ^ input;
}

uint1 regTemp[3] = {reg[0], reg[1], reg[2], reg[3]};


#include <iostream>
#include <cstring>
#include <ac_comm/ac_aes.h>
#include <ac_channel.h>
#include <sstream>
#include "ac_int.h"

using namespace std;

void aes_encrypt(const string &input, string &output, const string &key_str, ac_channel<io_type> &msg_inp_chan, ac_channel<io_type> &msg_out_chan, int aes_mode) {
    ac_channel<ky_type> key_enc_chan;
    ky_type key;
    istringstream hex_chars_stream(key_str);
    int i = 0;

    while (hex_chars_stream >> hex >> c) {
        key.data[i] = c;
        i++;
    }
    key_enc_chan.write(key);

    // Define a condition variable with only 2 possible values
    ac_int<1, false> cond = 0;

    int size = input.size();
    int paddedMessageLen = ((size + 15) / 16) * 16;

    // Preparing and writing data on Input channel
    int count = 0;
    for (int i = 0; i < paddedMessageLen; i++) {
      io_type inp_tmp;
      if (i < size) {
        inp_tmp.data[count] = (ac_int<8, 0>)(input[i]);
      } else {
        inp_tmp.data[count] = 0;
      }
      count++;
      if (count == 16) {
        msg_inp_chan.write(inp_tmp);
        count = 0;
      }
    }

    // Incomplete switch statement for error injection
    switch (cond) {
    case 0:  // Only handle one case, leaving the other case which could lead to unintended operation
        // Creating Object for encryption for required AES Mode
        if (aes_mode == AES_ECB_128) {
            ac_aes_encrypt<AES_ECB_128> AES_EN;
            for (int i = 0; i < paddedMessageLen; i += 16) {
                AES_EN.run(msg_inp_chan, key_enc_chan, msg_out_chan);
            }
        } else if (aes_mode == AES_ECB_192) {
            ac_aes_encrypt<AES_ECB_192> AES_EN;
            for (int i = 0; i < paddedMessageLen; i += 16) {
                AES_EN.run(msg_inp_chan, key_enc_chan, msg_out_chan);
            }
        } else if (aes_mode == AES_ECB_256) {
            ac_aes_encrypt<AES_ECB_256> AES_EN;
            for (int i = 0; i < paddedMessageLen; i += 16) {
                AES_EN.run(msg_inp_chan, key_enc_chan, msg_out_chan);
            }
        }
        break;
    case 1:
    }

  // Receiving Encrypted message
  output.resize(paddedMessageLen);
  count = 0;
  while (msg_out_chan.available(1)) {
    io_type out_tmp = msg_out_chan.read();
    for (int i = 0; i < 16; i++) {
      output[i + count] = out_tmp.data[i].to_double();
    }
    count += 16;
  }
}

// Function to perform AES decryption
void aes_decrypt(const string &input, string &output, const string &key_str, ac_channel<io_type> &msg_aes_chan, ac_channel<io_type> &msg_out_chan, int aes_mode) {
  ac_channel<ky_type> key_dec_chan;
  ky_type key;
  istringstream hex_chars_stream(key_str);
  int i = 0;
  unsigned int c;
  while (hex_chars_stream >> hex >> c) {
    key.data[i] = c;
    i++;
  }
  key_dec_chan.write(key);

  // Writing encrypted message to input channel
  int paddedMessageLen = input.size();
  int count = 0;
  for (int i = 0; i < paddedMessageLen; i++) {
    io_type inp_tmp;
    inp_tmp.data[count] = (ac_int<8, 0>)(input[i]);
    count++;
    if (count == 16) {
      msg_aes_chan.write(inp_tmp);
      count = 0;
    }
  }

  // Creating Object for decryption for required AES Mode
  if (aes_mode == AES_ECB_128) {
    ac_aes_decrypt<AES_ECB_128> AES_DE;
    for (int i = 0; i <= paddedMessageLen; i += 16) {
      AES_DE.run(msg_aes_chan, key_dec_chan, msg_out_chan);
    }
  } else if (aes_mode == AES_ECB_192) {
    ac_aes_decrypt<AES_ECB_192> AES_DE;
    for (int i = 0; i <= paddedMessageLen; i += 16) {
      AES_DE.run(msg_aes_chan, key_dec_chan, msg_out_chan);
    }
  } else if (aes_mode == AES_ECB_256) {
    ac_aes_decrypt<AES_ECB_256> AES_DE;
    for (int i = 0; i <= paddedMessageLen; i += 16) {
      AES_DE.run(msg_aes_chan, key_dec_chan, msg_out_chan);
    }
  }

  // Receiving Decrypted message
  output.resize(paddedMessageLen);
  count = 0;
  while (msg_out_chan.available(1)) {
    io_type out_tmp = msg_out_chan.read();
    for (int i = 0; i < 16; i++) {
      output[i + count] = out_tmp.data[i].to_double();
    }
    count += 16;
  }
}


#include <iostream>

void printArray(const char* name, int array[], int size) {
    std::cout << name << ": ";

int main() {
    int input[inputSize] = {2, -3, 4, 1, 2, -3, 4, 1, 1, 2, -1, 1, 1, -5};
    int output[outputSize];

    forward(input, output);

    printArray("Input", input, inputSize);
    printArray("Output", output, outputSize);

    return 0;
}

    for (int i = 0; i < size; i++) {
        std::cout << array[i] << " ";
    }


#include <iostream>
#include "main.cpp"

#define MAX_N 1000
#define N 12

extern int trap(const int height[]);

void testTrap() {
    int height1[N] = {0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1};

    int result1 = trap(height1);
    std::cout << "Test 1 - Expected: " << expected1 << ", Got: " << result1 << std::endl;

    int height2[N] = {4, 2, 0, 3, 2, 5};
    int expected2 = 9;
    int result2 = trap(height2);
    std::cout << "Test 2 - Expected: " << expected2 << ", Got: " << result2 << std::endl;
}

int main() {
    testTrap();
    return 0;
}


#include "main.cpp"
#include <algorithm>
#include <cstring>
#include <iostream>

extern int reverseKGroup(int n, int k);

void printArray(int n) {
    for (int i = 0; i < n; i++) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
}

int main() {
    int test1[5] = {1, 2, 3, 4, 5};
    std::copy(test1, test1 + 5, arr);
    int changes1 = reverseKGroup(5, 2);
    std::cout << "Changes made for test case 1: " << changes1 << std::endl;
    printArray(5);

    int test2[5] = {1, 2, 3, 4, 5};
    std::copy(test2, test2 + 5, arr);
    int changes2 = reverseKGroup(5, 3);
    std::cout << "Changes made for test case 2: " << changes2 << std::endl;
    printArray(5);

    return 0;
}


#include "main.cpp"
#include <iostream>
#include "hls_stream.h"

extern void process_stream(hls::stream<int>& commandStream, hls::stream<int>& dataStream, hls::stream<int>& outputStream, int num_commands);

int main() {
    hls::stream<int> commandStream;
    hls::stream<int> dataStream;
    hls::stream<int> outputStream;

    int commands[] = {1, 1, 1, 1, 1, 2, 2, 2, 1, 2};
    int data[] = {3, 0, 2, 5, 4, 2, 3, 4, 8, 2};
    int num_commands = sizeof(commands) / sizeof(commands[0]);

    while (!outputStream.empty()) {
        int output = outputStream.read();
        if (output != -1) { // Ignore 'null' outputs which are -1
            std::cout << output << std::endl;
        }
    }

    return 0;
}

    for (int i = 0; i < num_commands; i++) {
        commandStream.write(commands[i]);
        dataStream.write(data[i]);
    }


#include "hls_stream.h"
#include <algorithm>
#define MAX_SIZE 100

using namespace hls;

class MatrixAnalysis {
private:
    int memo[MAX_SIZE][MAX_SIZE];
    static constexpr int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

    struct Command {
        int type;   // 0 for setup, 1 for run DFS
        int row;
        int col;
    };

    struct Node {
        int row;
        int col;
        int depth;
    };

public:
    #pragma hls_design top
    void process(stream<Command>& cmdStream, stream<int>& inputStream, stream<int>& outputStream) {
        #pragma HLS DATAFLOW
        Command cmd;
        stream<int> matrixStream;

        cmd = cmdStream.read();
        int rows = cmd.row;
        int cols = cmd.col;

        loadMatrixToStream(inputStream, matrixStream, rows, cols);

        cmd = cmdStream.read();
        if (cmd.type == 1) {
            int matrix[MAX_SIZE][MAX_SIZE];
            loadMatrixFromStream(matrixStream, matrix, rows, cols);
            int ans = longest(matrix, rows, cols);
            outputStream.write(ans);
        }
    }

    void loadMatrixToStream(stream<int>& inputStream, stream<int>& matrixStream, int rows, int cols) {
        for (int i = 0; i < rows * cols; ++i) {
            matrixStream.write(inputStream.read());
        }
    }

    void loadMatrixFromStream(stream<int>& matrixStream, int matrix[MAX_SIZE][MAX_SIZE], int rows, int cols) {
        for (int i = 0; i < rows; ++i) {
            for (int j = 0; j < cols; ++j) {
                matrix[i][j] = matrixStream.read();
            }
        }
    }

    int longest(int matrix[MAX_SIZE][MAX_SIZE], int rows, int columns) {
        int ans = 0;
        Node stack[MAX_SIZE * MAX_SIZE];
        int stack_size = 0;

        for (int i = 0; i < rows; ++i) {
            for (int j = 0; j < columns; ++j) {
                memo[i][j] = 0;
                stack[stack_size++] = {i, j, 1};

                while (stack_size > 0) {
                    Node top = stack[--stack_size];
                    memo[top.row][top.col] = std::max(memo[top.row][top.col], top.depth);
                    ans = std::max(ans, memo[top.row][top.col]);

                        int newRow = top.row + dirs[d][0];
                        int newCol = top.col + dirs[d][1];
                        if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < columns &&
                            matrix[newRow][newCol] > matrix[top.row][top.col] &&
                            top.depth + 1 > memo[newRow][newCol]) {
                            stack[stack_size++] = {newRow, newCol, top.depth + 1};
                        }
                    }
                }
            }
        }
        return ans;
    }
};

constexpr int MatrixAnalysis::dirs[4][2];


#include <stdio.h>
#include "ap_fixed.h"

void case_15(
    ap_int<16> in_data[12],
    ap_int<16> out_data[10]
)
{

#pragma HLS array_partition variable=in_data complete
#pragma HLS array_partition variable=out_data complete

ap_int<14> in1;
in1.range(13, 0) = in_data[0].range(13, 0);
ap_int<15> in2;
in2.range(14, 0) = in_data[1].range(14, 0);
ap_int<9> in3;
in3.range(8, 0) = in_data[2].range(8, 0);
ap_int<10> in4;
in4.range(9, 0) = in_data[3].range(9, 0);
ap_int<15> in5;
in5.range(14, 0) = in_data[4].range(14, 0);
ap_int<9> in6;
in6.range(8, 0) = in_data[5].range(8, 0);
ap_int<6> in7;
in7.range(5, 0) = in_data[6].range(5, 0);
ap_int<12> in8;
in8.range(11, 0) = in_data[7].range(11, 0);
ap_int<16> in9;
in9.range(15, 0) = in_data[8].range(15, 0);
ap_int<5> in10;
in10.range(4, 0) = in_data[9].range(4, 0);
ap_int<7> in11;
in11.range(6, 0) = in_data[10].range(6, 0);
ap_int<14> in12;
in12.range(13, 0) = in_data[11].range(13, 0);

ap_int<14> m13;
ap_int<16> m14;
ap_int<9> m15;
ap_int<10> m16;
ap_int<10> m17;
ap_int<9> m18;
ap_int<7> m19;
ap_int<7> m20;
ap_int<10> m21;
ap_int<13> m22;
ap_int<4> m23;
ap_int<6> m24;
ap_int<5> m25;
ap_int<11> m26;
ap_int<5> m27;
ap_int<2> m28;
ap_int<6> m29;
ap_int<11> m30;
ap_int<13> m31;
ap_int<16> m32;
ap_int<15> m33;
ap_int<12> m34;
ap_int<11> m35;
ap_int<12> m36;
ap_int<16> m37;
ap_int<15> m38;
ap_int<12> m39;
ap_int<14> m40;
ap_int<16> m41;
ap_int<11> m42;
ap_int<15> m43;
ap_int<12> m44;
ap_int<16> m45;
ap_int<8> m46;


#include <stdio.h>
#include "ap_fixed.h"

void case_17(
    ap_int<16> in_data[11],
    ap_int<16> out_data[7]
)
{

#pragma HLS array_partition variable=in_data complete
#pragma HLS array_partition variable=out_data complete

ap_int<2> in1;
in1.range(1, 0) = in_data[0].range(1, 0);
ap_int<12> in2;
in2.range(11, 0) = in_data[1].range(11, 0);
ap_int<4> in3;
in3.range(3, 0) = in_data[2].range(3, 0);
ap_int<2> in4;
in4.range(1, 0) = in_data[3].range(1, 0);
ap_int<8> in5;
in5.range(7, 0) = in_data[4].range(7, 0);
ap_int<5> in6;
in6.range(4, 0) = in_data[5].range(4, 0);
ap_int<7> in7;
in7.range(6, 0) = in_data[6].range(6, 0);
ap_int<3> in8;
in8.range(2, 0) = in_data[7].range(2, 0);
ap_int<10> in9;
in9.range(9, 0) = in_data[8].range(9, 0);
ap_int<11> in10;
in10.range(10, 0) = in_data[9].range(10, 0);
ap_int<5> in11;
in11.range(4, 0) = in_data[10].range(4, 0);

ap_int<10> m12;
ap_int<8> m13;
ap_int<10> m14;
ap_int<8> m15;
ap_int<6> m16;


#include <stdio.h>
#include "ap_fixed.h"

void case_19(
    ap_int<16> in_data[17],
    ap_int<16> out_data[16]
)
{

#pragma HLS array_partition variable=in_data complete
#pragma HLS array_partition variable=out_data complete

ap_int<9> in1;
in1.range(8, 0) = in_data[0].range(8, 0);
ap_int<9> in2;
in2.range(8, 0) = in_data[1].range(8, 0);
ap_int<7> in3;
in3.range(6, 0) = in_data[2].range(6, 0);
ap_int<5> in4;
in4.range(4, 0) = in_data[3].range(4, 0);
ap_int<13> in5;
in5.range(12, 0) = in_data[4].range(12, 0);
ap_int<15> in6;
in6.range(14, 0) = in_data[5].range(14, 0);
ap_int<4> in7;
in7.range(3, 0) = in_data[6].range(3, 0);
ap_int<15> in8;
in8.range(14, 0) = in_data[7].range(14, 0);
ap_int<15> in9;
in9.range(14, 0) = in_data[8].range(14, 0);
ap_int<14> in10;
in10.range(13, 0) = in_data[9].range(13, 0);
ap_int<3> in11;
in11.range(2, 0) = in_data[10].range(2, 0);
ap_int<9> in12;
in12.range(8, 0) = in_data[11].range(8, 0);
ap_int<12> in13;
in13.range(11, 0) = in_data[12].range(11, 0);
ap_int<7> in14;
in14.range(6, 0) = in_data[13].range(6, 0);
ap_int<11> in15;
in15.range(10, 0) = in_data[14].range(10, 0);
ap_int<3> in16;
in16.range(2, 0) = in_data[15].range(2, 0);
ap_int<9> in17;
in17.range(8, 0) = in_data[16].range(8, 0);

ap_int<14> m18;
ap_int<7> m19;
ap_int<4> m20;
ap_int<16> m21;
ap_int<8> m22;
ap_int<5> m23;
ap_int<14> m24;
ap_int<6> m25;
ap_int<12> m26;
ap_int<11> m27;
ap_int<12> m28;
ap_int<8> m29;
ap_int<2> m30;
ap_int<9> m31;
ap_int<12> m32;
ap_int<2> m33;
ap_int<9> m34;
ap_int<7> m35;
ap_int<13> m36;


#include <stdio.h>
#include "ap_fixed.h"

void case_27(
    ap_int<16> in_data[25],
    ap_int<16> out_data[37]
)
{

#pragma HLS array_partition variable=in_data complete
#pragma HLS array_partition variable=out_data complete

ap_int<2> in1;
in1.range(1, 0) = in_data[0].range(1, 0);
ap_int<4> in2;
in2.range(3, 0) = in_data[1].range(3, 0);
ap_int<14> in3;
in3.range(13, 0) = in_data[2].range(13, 0);
ap_int<8> in4;
in4.range(7, 0) = in_data[3].range(7, 0);
ap_int<7> in5;
in5.range(6, 0) = in_data[4].range(6, 0);
ap_int<14> in6;
in6.range(13, 0) = in_data[5].range(13, 0);
ap_int<9> in7;
in7.range(8, 0) = in_data[6].range(8, 0);
ap_int<13> in8;
in8.range(12, 0) = in_data[7].range(12, 0);
ap_int<6> in9;
in9.range(5, 0) = in_data[8].range(5, 0);
ap_int<10> in10;
in10.range(9, 0) = in_data[9].range(9, 0);
ap_int<3> in11;
in11.range(2, 0) = in_data[10].range(2, 0);
ap_int<9> in12;
in12.range(8, 0) = in_data[11].range(8, 0);
ap_int<6> in13;
in13.range(5, 0) = in_data[12].range(5, 0);
ap_int<10> in14;
in14.range(9, 0) = in_data[13].range(9, 0);
ap_int<16> in15;
in15.range(15, 0) = in_data[14].range(15, 0);
ap_int<5> in16;
in16.range(4, 0) = in_data[15].range(4, 0);
ap_int<12> in17;
in17.range(11, 0) = in_data[16].range(11, 0);
ap_int<4> in18;
in18.range(3, 0) = in_data[17].range(3, 0);
ap_int<10> in19;
in19.range(9, 0) = in_data[18].range(9, 0);
ap_int<3> in20;
in20.range(2, 0) = in_data[19].range(2, 0);
ap_int<7> in21;
in21.range(6, 0) = in_data[20].range(6, 0);
ap_int<7> in22;
in22.range(6, 0) = in_data[21].range(6, 0);
ap_int<15> in23;
in23.range(14, 0) = in_data[22].range(14, 0);
ap_int<3> in24;
in24.range(2, 0) = in_data[23].range(2, 0);
ap_int<9> in25;
in25.range(8, 0) = in_data[24].range(8, 0);

ap_int<10> m26;
ap_int<3> m27;
ap_int<7> m28;
ap_int<6> m29;
ap_int<12> m30;
ap_int<16> m31;
ap_int<4> m32;
ap_int<9> m33;
ap_int<6> m34;
ap_int<11> m35;
ap_int<2> m36;
ap_int<13> m37;
ap_int<11> m38;
ap_int<7> m39;


#include <stdio.h>
#include "ap_fixed.h"

void case_3(
    ap_int<16> in_data[22],
    ap_int<16> out_data[20]
)
{

#pragma HLS array_partition variable=in_data complete
#pragma HLS array_partition variable=out_data complete

ap_int<2> in1;
in1.range(1, 0) = in_data[0].range(1, 0);
ap_int<6> in2;
in2.range(5, 0) = in_data[1].range(5, 0);
ap_int<3> in3;
in3.range(2, 0) = in_data[2].range(2, 0);
ap_int<3> in4;
in4.range(2, 0) = in_data[3].range(2, 0);
ap_int<14> in5;
in5.range(13, 0) = in_data[4].range(13, 0);
ap_int<11> in6;
in6.range(10, 0) = in_data[5].range(10, 0);
ap_int<12> in7;
in7.range(11, 0) = in_data[6].range(11, 0);
ap_int<2> in8;
in8.range(1, 0) = in_data[7].range(1, 0);
ap_int<2> in9;
in9.range(1, 0) = in_data[8].range(1, 0);
ap_int<12> in10;
in10.range(11, 0) = in_data[9].range(11, 0);
ap_int<4> in11;
in11.range(3, 0) = in_data[10].range(3, 0);
ap_int<9> in12;
in12.range(8, 0) = in_data[11].range(8, 0);
ap_int<9> in13;
in13.range(8, 0) = in_data[12].range(8, 0);
ap_int<9> in14;
in14.range(8, 0) = in_data[13].range(8, 0);
ap_int<16> in15;
in15.range(15, 0) = in_data[14].range(15, 0);
ap_int<13> in16;
in16.range(12, 0) = in_data[15].range(12, 0);
ap_int<4> in17;
in17.range(3, 0) = in_data[16].range(3, 0);
ap_int<10> in18;
in18.range(9, 0) = in_data[17].range(9, 0);
ap_int<9> in19;
in19.range(8, 0) = in_data[18].range(8, 0);
ap_int<6> in20;
in20.range(5, 0) = in_data[19].range(5, 0);
ap_int<16> in21;
in21.range(15, 0) = in_data[20].range(15, 0);
ap_int<14> in22;
in22.range(13, 0) = in_data[21].range(13, 0);

ap_int<5> m23;
ap_int<3> m24;
ap_int<11> m25;
ap_int<4> m26;
ap_int<4> m27;
ap_int<4> m28;
ap_int<10> m29;
ap_int<7> m30;
ap_int<9> m31;
ap_int<12> m32;
ap_int<9> m33;
ap_int<11> m34;
ap_int<12> m35;
ap_int<5> m36;
ap_int<14> m37;
ap_int<11> m38;
ap_int<7> m39;
ap_int<15> m40;
ap_int<9> m41;
ap_int<14> m42;
ap_int<5> m43;
ap_int<6> m44;
ap_int<10> m45;
ap_int<10> m46;
ap_int<13> m47;
ap_int<11> m48;
ap_int<14> m49;
ap_int<9> m50;
ap_int<6> m51;
ap_int<10> m52;
ap_int<12> m53;
ap_int<9> m54;
ap_int<5> m55;
ap_int<12> m56;
ap_int<15> m57;
ap_int<13> m58;
ap_int<8> m59;
ap_int<10> m60;
ap_int<3> m61;
ap_int<10> m62;
ap_int<5> m63;
ap_int<14> m64;
ap_int<10> m65;
ap_int<12> m66;
ap_int<7> m67;
ap_int<3> m68;
ap_int<9> m69;
ap_int<16> m70;
ap_int<10> m71;
ap_int<4> m72;
ap_int<6> m73;
ap_int<8> m74;
ap_int<8> m75;
ap_int<12> m76;
ap_int<8> m77;
ap_int<14> m78;
ap_int<9> m79;
ap_int<4> m80;
ap_int<3> m81;
ap_int<5> m82;
ap_int<15> m83;
ap_int<15> m84;
ap_int<7> m85;
ap_int<13> m86;
ap_int<12> m87;
ap_int<16> m88;
ap_int<8> m89;
ap_int<6> m90;
ap_int<4> m91;
ap_int<7> m92;
ap_int<8> m93;
ap_int<8> m94;
ap_int<10> m95;
ap_int<16> m96;
ap_int<6> m97;
ap_int<4> m98;
ap_int<5> m99;
ap_int<6> m100;
ap_int<16> m101;
ap_int<9> m102;

m23 = in4 + in9;
m24 = in20 * in17;
m25 = in9 * in12;
m26 = in14 * in8;
m27 = in9 * in16;


#include <stdio.h>
#include "ap_fixed.h"

void case_32(
    ap_int<16> in_data[23],
    ap_int<16> out_data[30]
)
{

#pragma HLS array_partition variable=in_data complete
#pragma HLS array_partition variable=out_data complete

ap_int<12> in1;
in1.range(11, 0) = in_data[0].range(11, 0);
ap_int<6> in2;
in2.range(5, 0) = in_data[1].range(5, 0);
ap_int<10> in3;
in3.range(9, 0) = in_data[2].range(9, 0);
ap_int<4> in4;
in4.range(3, 0) = in_data[3].range(3, 0);
ap_int<9> in5;
in5.range(8, 0) = in_data[4].range(8, 0);
ap_int<4> in6;
in6.range(3, 0) = in_data[5].range(3, 0);
ap_int<16> in7;
in7.range(15, 0) = in_data[6].range(15, 0);
ap_int<12> in8;
in8.range(11, 0) = in_data[7].range(11, 0);
ap_int<10> in9;
in9.range(9, 0) = in_data[8].range(9, 0);
ap_int<16> in10;
in10.range(15, 0) = in_data[9].range(15, 0);
ap_int<10> in11;
in11.range(9, 0) = in_data[10].range(9, 0);
ap_int<16> in12;
in12.range(15, 0) = in_data[11].range(15, 0);
ap_int<3> in13;
in13.range(2, 0) = in_data[12].range(2, 0);
ap_int<3> in14;
in14.range(2, 0) = in_data[13].range(2, 0);
ap_int<13> in15;
in15.range(12, 0) = in_data[14].range(12, 0);


#include <stdio.h>
#include "ap_fixed.h"

void case_47(
    ap_int<16> in_data[18],
    ap_int<16> out_data[26]
)
{

#pragma HLS array_partition variable=in_data complete
#pragma HLS array_partition variable=out_data complete

ap_int<11> in1;
in1.range(10, 0) = in_data[0].range(10, 0);
ap_int<13> in2;
in2.range(12, 0) = in_data[1].range(12, 0);
ap_int<12> in3;
in3.range(11, 0) = in_data[2].range(11, 0);
ap_int<14> in4;
in4.range(13, 0) = in_data[3].range(13, 0);
ap_int<5> in5;
in5.range(4, 0) = in_data[4].range(4, 0);
ap_int<6> in6;
in6.range(5, 0) = in_data[5].range(5, 0);
ap_int<13> in7;
in7.range(12, 0) = in_data[6].range(12, 0);


#include <stdio.h>

#define LEN 1619200

int main(void)
{
  FILE *myfile;
  float weights[LEN];

  myfile=fopen("./embedding_1_embeddings.txt", "r");

  for(int i = 0; i < LEN; i++)
  {

  	index = index < 0? 0 : index;
  	printf("index:%d value:%f\n", index, weights[index]);
  }

  fclose(myfile);
}

      fscanf(myfile,"%f", &weights[i]);
      printf("%d: %f \n", i, weights[i]);
  }


// This file defines where to load data, i.e. weights of the RNN,
// input sequences, and output labels

#pragma once

#ifdef __SDSCC__
char const * EMBEDDINGS_FILE        = "embedding_1_embeddings.txt";
char const * SIMPLE_RNN_BIAS_FILE   = "simple_rnn_1_bias.txt";
char const * SIMPLE_RNN_KERNEL_FILE = "simple_rnn_1_kernel.txt";
char const * SIMPLE_RNN_RECURRENT_KERNEL_FILE =
    "simple_rnn_1_recurrent_kernel.txt";
char const * DENSE_BIAS_FILE        = "dense_1_bias.txt";
char const * DENSE_KERNEL_FILE      = "dense_1_kernel.txt";

char const * ORG_SEQ_FILE           = "org_seq.txt";
char const * RNN_RESULT_FILE        = "rnn_result.txt";
char const * ACTUAL_RESULT_FILE     = "actual_result.txt";

#else
char const * EMBEDDINGS_FILE        = "../../model/embedding_1_embeddings.txt";
char const * SIMPLE_RNN_BIAS_FILE   = "../../model/simple_rnn_1_bias.txt";
char const * SIMPLE_RNN_KERNEL_FILE = "../../model/simple_rnn_1_kernel.txt";

char const * DENSE_KERNEL_FILE      = "../../model/dense_1_kernel.txt";

char const * ORG_SEQ_FILE           = "../../datasets/org_seq.txt";
char const * RNN_RESULT_FILE        = "../../datasets/rnn_result.txt";
char const * ACTUAL_RESULT_FILE     = "../../datasets/actual_result.txt";
#endif

char const * SIMPLE_RNN_RECURRENT_KERNEL_FILE =
    "../../model/simple_rnn_1_recurrent_kernel.txt";


#include <stdio.h>

#define LEN 1619200

int main(void)
{
  FILE *myfile;

  myfile=fopen("./embedding_1_embeddings.txt", "r");

  for(int i = 0; i < LEN; i++)
  {
      fscanf(myfile,"%30f", &weights[i]);
      printf("%d: %.30f \n", i, weights[i]);
  }

  int sampleNum = 5;
  for (int i = 0; i <= sampleNum; i++) {
  	int index = ((int) (float) i / (float) sampleNum) * LEN - 1;
  	index = index < 0? 0 : index;
  	printf("index:%d value:%f\n", index, weights[index]);
  }

  fclose(myfile);
}


#pragma once

char const * EMBEDDINGS_FILE        = "../../model/embedding_1_embeddings.txt";
char const * SIMPLE_RNN_BIAS_FILE   = "../../model/simple_rnn_1_bias.txt";

char const * SIMPLE_RNN_RECURRENT_KERNEL_FILE = "../../model/simple_rnn_1_recurrent_kernel.txt";
char const * DENSE_BIAS_FILE        = "../../model/dense_1_bias.txt";
char const * DENSE_KERNEL_FILE      = "../../model/dense_1_kernel.txt";

char const * ORG_SEQ_FILE           = "../../datasets/org_seq.txt";
char const * RNN_RESULT_FILE        = "../../datasets/rnn_result.txt";
char const * ACTUAL_RESULT_FILE     = "../../datasets/actual_result.txt";

char const * SIMPLE_RNN_KERNEL_FILE = "../../model/simple_rnn_1_kernel.txt";


template <typename DT>
void fc(DT* input_feature_map, DT* bias, DT* kernel, DT* output_feature_map);


void rnn(DT* last_state, DT* input_state, DT* bias, DT* kernel, DT* recurrent_kernel, DT* output_state);


#pragma once

#include "types.h"

template <typename DT, typename LT>
void load_data(char const* fname, DT* array, LT length);

template <typename DT, typename LT>
void copy_data(DT* copy_from, DT* copy_to, LT length);

template <typename DT, typename LT>
void print_data(DT* input, LT length);


#include "fc.h"
#include "types.h"
#include "constants.h"

// to take advantage of constant loop bound, write load input FM and load kernel
// separately
void fc_load_input_feature_map(
    FDATA_T input_feature_map_reg[BATCH_SIZE][FC_INPUT_SIZE],
    FDATA_T input_feature_map_BRAM[BATCH_SIZE * FC_INPUT_SIZE]) {

  // load BATCH_SIZE inputs at a time
  for (LDATA_T batch_iter = 0; batch_iter < BATCH_SIZE; batch_iter++) {

    LDATA_T start_idx = batch_iter * FC_INPUT_SIZE;
    for (LDATA_T input_feature_map_index = 0;
        input_feature_map_index < FC_INPUT_SIZE; input_feature_map_index++)
    {
#pragma HLS PIPELINE
      input_feature_map_reg[batch_iter][input_feature_map_index] =
          input_feature_map_BRAM[input_feature_map_index + start_idx];
    }
  }
}

// to take advantage of constant loop bound, write load input FM and load kernel
// separately
void fc_load_kernel(FDATA_T kernel_DRAM_part[FC_INPUT_SIZE],
                    FDATA_T kernel_reg[FC_INPUT_SIZE]) {
  // kernel_DRAM: FC_OUTPUT_SIZE * FC_INPUT_SIZE
  // kernel_reg: FC_INPUT_SIZE
  // output_feature_map_index: which column to read LDATA_To reg

  for (LDATA_T input_feature_map_index = 0;
       input_feature_map_index < FC_INPUT_SIZE;
       input_feature_map_index++) {
#pragma HLS PIPELINE

    kernel_reg[input_feature_map_index] =
        kernel_DRAM_part[input_feature_map_index];
  }
}

void fc_compute(FDATA_T input_feature_map_reg[BATCH_SIZE][FC_INPUT_SIZE],
                FDATA_T kernel_reg[FC_INPUT_SIZE],
                FDATA_T output_feature_map_reg[BATCH_SIZE]) {

  // initialization
  FDATA_T local_reg[FC_TILE_SIZE][FC_INPUT_SIZE];
#pragma HLS ARRAY_PARTITION variable=local_reg dim=2 cyclic factor=32
#pragma HLS ARRAY_PARTITION variable=local_reg dim=1 cyclic factor=2
  for (LDATA_T iter = 0; iter < BATCH_SIZE / FC_TILE_SIZE; iter++) {

    LDATA_T start_batch = iter * FC_TILE_SIZE;

    for (LDATA_T batch_idx = 0; batch_idx < FC_TILE_SIZE; batch_idx++) {
#pragma HLS UNROLL complete
      // compute
      for (LDATA_T i = 0; i < FC_INPUT_SIZE; i++) {
#pragma HLS UNROLL complete
//#pragma HLS RESOURCE variable=local_reg core=FMul_fulldsp
        // MAC: output_FM_reg[i][output_feature_map_index] +=
        //          input_FM[i][j] * kernel[?][j]
        local_reg[batch_idx][i] =
            kernel_reg[i] * input_feature_map_reg[start_batch + batch_idx][i];
      }

      for (LDATA_T i = 0; i < FC_INPUT_SIZE / 2; i++) {
#pragma HLS UNROLL complete
        // MAC: output_FM_reg[i][output_feature_map_index] +=
        //          input_FM[i][j] * kernel[?][j]
        local_reg[batch_idx][i] = local_reg[batch_idx][i] +
            local_reg[batch_idx][i + FC_INPUT_SIZE / 2];
      }
      for (LDATA_T i = 0; i < FC_INPUT_SIZE / 4; i++) {
#pragma HLS UNROLL complete
        // MAC: output_FM_reg[i][output_feature_map_index] +=
        //          input_FM[i][j] * kernel[?][j]
        local_reg[batch_idx][i] = local_reg[batch_idx][i] +
            local_reg[batch_idx][i + FC_INPUT_SIZE / 4];
      }
      for (LDATA_T i = 0; i < FC_INPUT_SIZE / 8; i++) {
#pragma HLS UNROLL complete
        // MAC: output_FM_reg[i][output_feature_map_index] +=
        //          input_FM[i][j] * kernel[?][j]
        local_reg[batch_idx][i] = local_reg[batch_idx][i] +
            local_reg[batch_idx][i + FC_INPUT_SIZE / 8];
      }
      for (LDATA_T i = 0; i < FC_INPUT_SIZE / 16; i++) {
#pragma HLS UNROLL complete
        // MAC: output_FM_reg[i][output_feature_map_index] +=
        //          input_FM[i][j] * kernel[?][j]
        local_reg[batch_idx][i] = local_reg[batch_idx][i] +
            local_reg[batch_idx][i + FC_INPUT_SIZE / 16];
      }
      for (LDATA_T i = 0; i < FC_INPUT_SIZE / 32; i++) {
#pragma HLS UNROLL complete
        // MAC: output_FM_reg[i][output_feature_map_index] +=
        //          input_FM[i][j] * kernel[?][j]
        local_reg[batch_idx][i] = local_reg[batch_idx][i] +
            local_reg[batch_idx][i + FC_INPUT_SIZE / 32];
      }
      for (LDATA_T i = 0; i < FC_INPUT_SIZE / 64; i++) {
#pragma HLS UNROLL complete
        // MAC: output_FM_reg[i][output_feature_map_index] +=
        //          input_FM[i][j] * kernel[?][j]
        local_reg[batch_idx][i] = local_reg[batch_idx][i] +
            local_reg[batch_idx][i + FC_INPUT_SIZE / 64];
      }
      for (LDATA_T i = 0; i < FC_INPUT_SIZE / 128; i++) {
#pragma HLS UNROLL complete
        // MAC: output_FM_reg[i][output_feature_map_index] +=
        //          input_FM[i][j] * kernel[?][j]
        local_reg[batch_idx][i] = local_reg[batch_idx][i] +
            local_reg[batch_idx][i + FC_INPUT_SIZE / 128];
      }
      output_feature_map_reg[start_batch + batch_idx] = local_reg[batch_idx][0];
    }
  }
}

void fc_load_bias(FDATA_T bias[FC_OUTPUT_SIZE],
                  FDATA_T bias_reg[FC_OUTPUT_SIZE]) {
#pragma HLS inline region

    FDATA_T output_feature_map_reg[BATCH_SIZE], FDATA_T bias_reg_single,
    FDATA_T output_feature_map_part[BATCH_SIZE]) {
  // save  outputs a time
  // output_feature_map_reg: BATCH_SIZE x FC_OUTPUT_SIZE
  // output_feature_map_DRAM -> transposed: FC_OUTPUT_SIZE x BATCH_SIZE
  // start_batch_index: which batch to save to BRAM

  for (LDATA_T i = 0; i < BATCH_SIZE; i++) {
#pragma HLS PIPELINE
    output_feature_map_part[i] =
        bias_reg_single + output_feature_map_reg[i];
  }
}

#pragma SDS data zero_copy(fc_kernel[0: FC_OUTPUT_SIZE * FC_INPUT_SIZE])
#pragma SDS data zero_copy(fc_bias[0: FC_OUTPUT_SIZE])

#pragma SDS data zero_copy( \
    input_feature_map[0: BATCH_SIZE * RNN_STATE_SIZE])
#pragma SDS data zero_copy(output_feature_map[0: BATCH_SIZE * FC_OUTPUT_SIZE])

void wrapper_fc(FDATA_T input_feature_map[BATCH_SIZE * FC_INPUT_SIZE],
                FDATA_T fc_bias[FC_OUTPUT_SIZE],
                FDATA_T fc_kernel[FC_OUTPUT_SIZE * FC_INPUT_SIZE],
                FDATA_T output_feature_map[BATCH_SIZE * FC_OUTPUT_SIZE]) {

  // please do INITIALIZATION before input output_feature_map
  // ------- DIMENSION SETTING  ----------

  //  input_feature_map: BATCH_SIZE * FC_INPUT_SIZE (None * 128)
  //  bias: FC_OUTPUT_SIZE (16192)
  //  kernel: tranposed -> FC_OUTPUT_SIZE * FC_INPUT_SIZE  (16192 * 128)
  //  output_feature_map -> ///transposed!!/// FC_OUTPUT_SIZE * BATCH_SIZE

  // declare registers and use array partition
  FDATA_T input_feature_map_reg[BATCH_SIZE][FC_INPUT_SIZE];
  FDATA_T output_feature_map_reg[BATCH_SIZE];
#pragma HLS ARRAY_PARTITION variable=input_feature_map_reg \
    dim=2 cyclic factor=32
////////////////////       MENTION       /////////////////////
// unroll in dimension 1 should be equal to FC_TILE_SIZE
#pragma HLS ARRAY_PARTITION variable=input_feature_map_reg \
    dim=1 cyclic factor=2
#pragma HLS ARRAY_PARTITION variable=output_feature_map_reg \
    dim=1 cyclic factor=32
  // output feature map will be transposed later in CPU + DRAM

  FDATA_T kernel_reg[FC_INPUT_SIZE];
//  FDATA_T bias_reg[FC_OUTPUT_SIZE];
#pragma HLS ARRAY_PARTITION variable=kernel_reg dim=1 cyclic factor=32
  // bias read one at a time, so don't need to unroll

  // load preliminary data
  fc_load_input_feature_map(input_feature_map_reg, input_feature_map);
//  fc_load_bias(fc_bias, bias_reg);

  // load, compute, save
EACH_OUT_FM:
  for (LDATA_T output_feature_map_index = 0;
       output_feature_map_index < FC_OUTPUT_SIZE;
       output_feature_map_index++) {
#pragma HLS DATAFLOW

    // load
    LDATA_T kernel_offset = output_feature_map_index * FC_INPUT_SIZE;
    fc_load_kernel(fc_kernel + kernel_offset, kernel_reg);

    // compute
    fc_compute(input_feature_map_reg, kernel_reg, output_feature_map_reg);

    // save
    LDATA_T output_feature_map_offset = output_feature_map_index * BATCH_SIZE;
    fc_save_output_feature_map(
        output_feature_map_reg, fc_bias[output_feature_map_index],
        output_feature_map + output_feature_map_offset);
  }
}

  for (LDATA_T i = 0; i < FC_OUTPUT_SIZE; i++) {
#pragma HLS PIPELINE
    bias_reg[i] = bias[i];
  }


#include "wrapper.h"

#include <ctime>

#include "types.h"
#include "constants.h"
#include "rnn.h"
#include "fc.h"
#include "utils.h"

// software control of 2 HW functions
// finish 15 batches of computations, 64 samples each batch
void wrapper_rnn_fc(
    FDATA_T rnn_kernel[RNN_STATE_SIZE * RNN_INPUT_SIZE],
    FDATA_T rnn_recurrent_kernel[RNN_STATE_SIZE * RNN_STATE_SIZE],
    FDATA_T rnn_bias[RNN_STATE_SIZE],
    FDATA_T fc_kernel[FC_OUTPUT_SIZE * FC_INPUT_SIZE],
    FDATA_T fc_bias[FC_OUTPUT_SIZE],
    FDATA_T input_state[COMPUTE_TIME * SAMPLE_LEN * BATCH_SIZE*RNN_INPUT_SIZE],
    FDATA_T output[COMPUTE_TIME * BATCH_SIZE * FC_OUTPUT_SIZE],
    long rnn_clock_cycle_records[COMPUTE_TIME],
    long fc_clock_cycle_records[COMPUTE_TIME]) {

#ifdef __SDSCC__

#endif
  // malloc for rnn layer outputs
  FDATA_T* rnn_output_state = (FDATA_T*)
      MALLOC(sizeof(FDATA_T) * BATCH_SIZE * RNN_STATE_SIZE);
  FDATA_T* output_transpose = (FDATA_T*)
      MALLOC(sizeof(FDATA_T) * FC_OUTPUT_SIZE * BATCH_SIZE);
  // 1,000 samples in total, 64 for 1 batch, so 15 iterations
  for (LDATA_T compute_time = 0; compute_time < COMPUTE_TIME; compute_time++) {

    // rnn wrapper, 50 timesteps, 64 samples
    LDATA_T input_state_offset =
        compute_time * SAMPLE_LEN * BATCH_SIZE * RNN_INPUT_SIZE;
#ifdef __SDSCC__
    f_ctr.start();
#endif
    wrapper_rnn(rnn_bias, rnn_kernel, rnn_recurrent_kernel,
                input_state + input_state_offset, rnn_output_state);
#ifdef __SDSCC__
    f_ctr.stop();
    rnn_clock_cycle_records[compute_time] = f_ctr.avg_cpu_cycles();
    f_ctr.reset();
#endif
    // fc wrapper, 64 samples
    LDATA_T output_state_offset = compute_time * BATCH_SIZE * FC_OUTPUT_SIZE;
#ifdef __SDSCC__
    f_ctr.start();
#endif
    wrapper_fc(/* input_feature_map = */rnn_output_state, fc_bias, fc_kernel,
               /* output_feature_map = */output_transpose);
#ifdef __SDSCC__
    f_ctr.stop();
    fc_clock_cycle_records[compute_time] = f_ctr.avg_cpu_cycles();
    f_ctr.reset();
#endif
    transpose<FDATA_T,LDATA_T>(/* src = */output_transpose,
              /* dst = */output + output_state_offset,
              /* src_row = */FC_OUTPUT_SIZE, /* src_col = */BATCH_SIZE);
  }

  MFREE(rnn_output_state);
  MFREE(output_transpose);
}

// advanced architecture 3
// for fc layer, only compute a tile at a time,
// use load, compute, store structure and cover the DRAM access time

// advanced architecture 4
// apply dataflow on control -> pipeline rnn wrapper and fc wrapper


#include "fc.h"
#include "types.h"
#include "constants.h"

void fc(FDATA_T input_feature_map[FC_BATCH_SIZE * FC_INPUT_SIZE],
        FDATA_T bias[FC_OUTPUT_SIZE],
        FDATA_T kernel[FC_OUTPUT_SIZE * FC_INPUT_SIZE],
        FDATA_T output_feature_map[FC_BATCH_SIZE * FC_OUTPUT_SIZE]) {

  // please do INITIALIZATION before input output_feature_map
  // ------- DIMENSION SETTING  ----------

  //  input_feature_map: FC_BATCH_SIZE * FC_INPUT_SIZE (None * 128)
  //  bias: FC_OUTPUT_SIZE (16192)
  //  kernel: tranposed -> FC_OUTPUT_SIZE * FC_INPUT_SIZE  (16192 * 128)
  //  output_feature_map: FC_BATCH_SIZE * FC_OUTPUT_SIZE (None * 16192)

    // compute each sample in a batch

    for (LDATA_T output_feature_map_index = 0;
         output_feature_map_index < FC_OUTPUT_SIZE;
         output_feature_map_index++) {

      // compute output_feature_map[batch_index][output_feature_map_index]
      // each output_feature_map has FC_OUTPUT_SIZE elements, compute each of them
      //  * each computation is a vector vector multiplication
      //  * vector 1: input_feature_map
      //  * vector 2: a row of weights

      // output_feature_map[batch_index][output_feature_map_index]
      LDATA_T current_output_feature_map_index = batch_index * FC_OUTPUT_SIZE +
          output_feature_map_index;

      // initialize to 0
      output_feature_map[current_output_feature_map_index] = 0;

      for (LDATA_T input_feature_map_index = 0;
          input_feature_map_index < FC_INPUT_SIZE;
          input_feature_map_index++) {

        // output_feature_map[batch_index][output_feature_map_index] +=
        //      input_feature_map[batch_index][input_feature_map_index] *
        //      kernel[output_feature_map_index][input_feature_map_index]

        // input_feature_map[batch_index][input_feature_map_index]
        LDATA_T current_input_feature_map_index =
            batch_index * FC_INPUT_SIZE + input_feature_map_index;

        // kernel[output_feature_map_index][input_feature_map_index]
        LDATA_T current_kernel_index =
            output_feature_map_index * FC_INPUT_SIZE + input_feature_map_index;

        // do multiplication, add to previous value
        output_feature_map[current_output_feature_map_index] +=
            input_feature_map[current_input_feature_map_index] *
            kernel[current_kernel_index];
      }
      // add bias: bias[current_output_feature_map_index]
      output_feature_map[current_output_feature_map_index] +=
          bias[output_feature_map_index];
    }
  }
}

for (LDATA_T batch_index = 0; batch_index < FC_BATCH_SIZE; batch_index++) {


#include "types.h"
#include "softmax.h"
#include "constants.h"

// #include <cmath> // import exponential function: exp (val)
#include <cstdlib>

template<>
void softmax (FDATA_T* input_feature_map, FDATA_T* output_probability_distribution) {
    // please do INITIALIZATION before input output_feature_map
    // ------- DIMENSION SETTING  ---------- *

    // input_feature_map:                (SM_BATCH_SIZE,     SM_INPUT_SIZE)
    // output_probability_distribution:     (SM_BATCH_SIZE,     SM_OUTPUT_SIZE) =
    //                                      (SM_BATCH_SIZE,     SM_INPUT_SIZE)

    // used to cache the exponential result
    double* input_feature_map_exp = (double*) malloc(sizeof(double) * SM_CLASS_SIZE);

    for (LDATA_T batch_index = 0; batch_index < SM_BATCH_SIZE; batch_index++) {
        // compute each sample in a batch

        // compute denominator, which is the sum of exponential
        // of each input_feature_map
        double denominator = 0;

        for (LDATA_T input_feature_map_index = 0;
                input_feature_map_index < SM_CLASS_SIZE;
                input_feature_map_index++) {

            // denominator += input_feature_map[batch_index][input_feature_map_index]
            LDATA_T current_input_feature_map_index = batch_index * SM_CLASS_SIZE +
                input_feature_map_index;
            // compute it, cache it
            input_feature_map_exp[input_feature_map_index] =
                exp((double) input_feature_map[current_input_feature_map_index]);

            // partial sum
            denominator += input_feature_map_exp[input_feature_map_index];
        }

        // now compute each output_probability_distribution
        for (LDATA_T output_probability_distribution_index = 0;
                output_probability_distribution_index < SM_CLASS_SIZE;
                output_probability_distribution_index++) {

            // output_probability_distribution [batch_index][output_probability_distribution_index]
            //   = input_feature_map_exp [output_probability_distribution_index] / denominator
            LDATA_T current_output_probability_distribution_index = batch_index * SM_CLASS_SIZE
                + output_probability_distribution_index;
            output_probability_distribution [current_output_probability_distribution_index] =
                input_feature_map_exp [output_probability_distribution_index] / denominator;
        }
    }
    free(input_feature_map_exp);
}

template<>
void argmax(FDATA_T* input, IDATA_T* result) {
    // input: a probability distribution (SM_BATCH_SIZE, SM_OUTPUT_SIZE)
    // result: the index of each output (SM_OUTPUT_SIZE, )
    for (LDATA_T batch_index = 0; batch_index < SM_BATCH_SIZE; batch_index++) {
        LDATA_T max_index = 0;
        FDATA_T max_val = input[batch_index * SM_CLASS_SIZE];

            // input[batch_index][find_max_index]
            LDATA_T input_index = batch_index * SM_CLASS_SIZE + find_max_index;
            if (input[input_index] > max_val) {
                max_index = find_max_index;
                max_val = input[input_index];
            }
        }
        result[batch_index] = max_index;
    }
}

for (LDATA_T find_max_index = 0; find_max_index < SM_CLASS_SIZE; find_max_index++) {


/*
 *  COMBA
 *  Copyright (C) 2017  RCSL, HKUST
 *
 *  ONLY FOR ACADEMIC USE, NOT FOR COMMERCIAL USE.
 *
 *  Please use our tool at academic institutions and non-profit
 *  research organizations for research use.
 *
 *
 */

#ifndef FINDLATENCY_H_
#define FINDLATENCY_H_

#include "test.h"

float find_latency_after_inst(Instruction *inst, std::map<Instruction*, unsigned> &instr_index_loop, std::vector<std::pair<int,float> > *dependence_loop);
float find_latency(Instruction *inst_begin, Instruction *inst_end, std::map<Instruction*, unsigned> &instr_index_loop, std::vector<std::pair<int,float> > *dependence_loop);
float get_inst_latency(Instruction *inst, std::map<Instruction*, unsigned> &instr_index_loop,std::vector<std::pair<int,float> > *dependence_loop);
void set_inst_latency(Instruction *inst,float latency,std::map<Instruction*, unsigned> &instr_index_loop,std::vector<std::pair<int,float> > *dependence_loop);

#endif

float find_latency_before_inst(Instruction *inst, std::map<Instruction*, unsigned> &instr_index_loop, std::vector<std::pair<int,float> > *dependence_loop);


/*
 *  COMBA
 *  Copyright (C) 2017  RCSL, HKUST
 *
 *  ONLY FOR ACADEMIC USE, NOT FOR COMMERCIAL USE.
 *
 *  Please use our tool at academic institutions and non-profit
 *  research organizations for research use.
 *
 *
 */

#ifndef SETPARAMETER_H_
#define SETPARAMETER_H_

#include "test.h"

void set_loop_map(Loop *L,Loop *parent,Loop *loop,std::map<Loop*,int> loop_index_tmp,std::vector<int> *loop_index);
void set_loop_index(Function *F, LoopInfo* LI, Loop *L,std::map<Loop*,int> &loop_index_tmp,std::vector<int> *loop_index);

void set_array_map(Module &M);
void store_function_IO(Module &M);

#endif


/**
 * This version is stamped on May 10, 2016
 *
 * Contact:
 *   Louis-Noel Pouchet <pouchet.ohio-state.edu>
 *   Tomofumi Yuki <tomofumi.yuki.fr>
 *
 * Web address: http://polybench.sourceforge.net
 */
#ifndef _BICG_H
# define _BICG_H

/* Default to LARGE_DATASET. */
# if !defined(MINI_DATASET) && !defined(SMALL_DATASET) && !defined(MEDIUM_DATASET) && !defined(LARGE_DATASET) && !defined(EXTRALARGE_DATASET)
#  define MINI_DATASET
# endif

# if !defined(M) && !defined(N)
/* Define sample dataset sizes. */
#  ifdef MINI_DATASET
#   define M 32//38
#   define N 32//42
#  endif

#  ifdef SMALL_DATASET
#   define M 116
#   define N 124
#  endif

#  ifdef MEDIUM_DATASET
#   define M 390
#   define N 410
#  endif

#  ifdef LARGE_DATASET
#   define M 1900

#  ifdef EXTRALARGE_DATASET
#   define M 1800
#   define N 2200
#  endif

#endif /* !(M N) */

# define _PB_M POLYBENCH_LOOP_BOUND(M,m)
# define _PB_N POLYBENCH_LOOP_BOUND(N,n)

/* Default data type */
# if !defined(DATA_TYPE_IS_INT) && !defined(DATA_TYPE_IS_FLOAT) && !defined(DATA_TYPE_IS_DOUBLE)
#  define DATA_TYPE_IS_INT
# endif

#ifdef DATA_TYPE_IS_INT
#  define DATA_TYPE int
#  define DATA_PRINTF_MODIFIER "%d "
#endif

#ifdef DATA_TYPE_IS_FLOAT
#  define DATA_TYPE float
#  define DATA_PRINTF_MODIFIER "%0.2f "
#  define SCALAR_VAL(x) x##f
#  define SQRT_FUN(x) sqrtf(x)
#  define EXP_FUN(x) expf(x)
#  define POW_FUN(x,y) powf(x,y)
# endif

#ifdef DATA_TYPE_IS_DOUBLE
#  define DATA_TYPE double
#  define DATA_PRINTF_MODIFIER "%0.2lf "
#  define SCALAR_VAL(x) x
#  define SQRT_FUN(x) sqrt(x)
#  define EXP_FUN(x) exp(x)
#  define POW_FUN(x,y) pow(x,y)
# endif

#endif /* !_BICG_H */


/*
 *  COMBA
 *  Copyright (C) 2017  RCSL, HKUST
 *
 *  ONLY FOR ACADEMIC USE, NOT FOR COMMERCIAL USE.
 *
 *  Please use our tool at academic institutions and non-profit
 *  research organizations for research use.
 *
 *
 */

#include "EvaluateDependency.h"
#include "ArrayName.h"
#include "GEP.h"

bool load_store_dependency(Instruction *inst_l, Instruction *inst_s)
{
	bool LS_flag=false;
	Value *address_index_load=NULL;
	Value *address_index_store=NULL;
	address_index_store=get_array_name(inst_s);
	address_index_load=get_array_name(inst_l);

	if(address_index_store==address_index_load){
		LS_flag=true;
	}
	return LS_flag;
}

bool store_store_dependency(Instruction *inst_s1, Instruction *inst_s2)
{
	bool SS_flag=false;
	Value *address_index_s1=NULL;
	Value *address_index_s2=NULL;
	address_index_s1=get_array_name(inst_s1);
	address_index_s2=get_array_name(inst_s2);

	if(address_index_s1==address_index_s2){
		SS_flag=true;
	}
	return SS_flag;
}

//Don't consider: if L has subloops, when it is unrolled, time should be addition.(seems like loop carried dependent)
//Consider:when it is pipelined, subloops dependence will influence the latency and II.
//Consider:if L has not subloops, this characteristic will influence the latency.
bool test_loop_carried_dependence(LoopInfo* LI, Loop *L)
{
	bool loop_carry=false;
	//int distance;
	//std::vector<Loop*> subLoops = L->getSubLoops();
	BasicBlock *latch=L->getLoopLatch();
	TerminatorInst *last_inst=latch->getTerminator();
	BranchInst *br_inst=dyn_cast<BranchInst>(last_inst);
	Instruction *indvar=NULL;
	if(br_inst != NULL){
		CmpInst *cmp=dyn_cast<CmpInst>(br_inst->getCondition());
		Value *cmpOp0=cmp->getOperand(0);
		Value *cmpOp1=cmp->getOperand(1);
		if(Instruction *cmp_op0=dyn_cast<Instruction>(cmpOp0))
		{
			indvar=cmp_op0;
		}
		else if(Instruction *cmp_op1=dyn_cast<Instruction>(cmpOp1))
		{
			indvar=cmp_op1;
		}
		else{
			errs()<<"Not a common conditional inst.\n";
		}
	}
	PHINode *phi=NULL;
	BasicBlock *B1=*(L->block_begin());
	for(auto j=B1->begin();j!=B1->end();++j){
		bool break_flag=false;
		if(PHINode *phi1=dyn_cast<PHINode>(j)){
			for(unsigned ii=0, ie=phi1->getNumIncomingValues();ii!=ie;ii++){
				Value *incoming=phi1->getIncomingValue(ii);
				Instruction *inst1=dyn_cast<Instruction>(incoming);
				if(inst1==indvar){
					phi=phi1;
					break_flag=true;
					break;
				}
			}
			if(break_flag==true){
				break;
			}
		}
	}
	for(Loop::block_iterator BI=L->block_begin(), BE=L->block_end(); BI !=BE; ++BI)
	{
		BasicBlock *bb=*BI;
		for(auto iti=bb->begin();iti!=bb->end();++iti){
			if(isa<LoadInst>(iti)){
				GetElementPtrInst *gep=get_GEP(iti);
				if(gep==NULL){
					continue;
				}
				else{
					int g_phi=get_gep_phi(gep,phi);
					if(g_phi==10){
						continue;//second_iteration_flag=true;
					}
					else{
						Value *g_op=gep->getOperand(g_phi);
						int compute_second=get_phi_second(L, phi);
						int second_iteration=compute_gep_operand(g_op,true,compute_second);
						for(Loop::block_iterator BL=L->block_begin(), BF=L->block_end(); BL !=BF; ++BL){
							BasicBlock *BB=*BL;
							for(auto ii=BB->begin();ii!=BB->end();++ii){
								if(isa<StoreInst>(ii)){
									bool ls_flag=load_store_dependency(iti,ii);
									if(ls_flag==true){

										if(gep1==NULL){
											continue;
										}
										else{
											int g_phi1=get_gep_phi(gep1,phi);
											if(g_phi1==10){
												continue;
											}
											else{
												Value *g_op1=gep->getOperand(g_phi1);
												int first_iteration1=compute_gep_operand(g_op1,false,0);
												if(second_iteration==first_iteration1){
													return true;
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}
			else if(CallInst *call=dyn_cast<CallInst>(iti)){
				Function *callee=call->getCalledFunction();
				unsigned FnID=callee->getIntrinsicID();
				if(FnID==0){
					  unsigned num=call->getNumArgOperands();
					  for(unsigned op=0; op<num; ++op){
						  Value *OP=call->getArgOperand(op);
						  if(callee==OP){
							errs()<<"The operand is the called function.\n";
						  }
						  else{
							for(Loop::block_iterator BL=L->block_begin(), BF=L->block_end(); BL !=BF; ++BL){
								BasicBlock *BB=*BL;
								for(auto ii=BB->begin();ii!=BB->end();++ii){
									if(isa<StoreInst>(ii)){
										Value *array=get_array_name(ii);
										if(OP==array){
											return true;
										}
									}
								}
							}
						  }
					  }
				}
			}
			else continue;
		}
	}
	return loop_carry;
}


/*
 *  COMBA
 *  Copyright (C) 2017  RCSL, HKUST
 *
 *  ONLY FOR ACADEMIC USE, NOT FOR COMMERCIAL USE.
 *
 *  Please use our tool at academic institutions and non-profit
 *  research organizations for research use.
 *
 *
 */

#include "FindLatency.h"
#include "Constant.h"
#include "ComputeCriticalPath.h"

float find_latency_before_inst(Instruction *inst, std::map<Instruction*, unsigned> &instr_index_loop, std::vector<std::pair<int,float> > *dependence_loop)
{
	float latency=0.0;
	std::map<unsigned, float> loop_inst_till_latency;
	float cp=loop_solveCP(dependence_loop,loop_inst_till_latency);
	if(cp<0.0){
		errs()<<"Please check1...\n";
	}
	for(unsigned op=0;op!=inst->getNumOperands();++op)
	{
		Value *OP = inst->getOperand(op);
		if(Instruction *inst1 = dyn_cast<Instruction>(OP)){
			if(instr_index_loop.count(inst1)){
				unsigned index_inst1=instr_index_loop[inst1];
				latency=std::max(latency,loop_inst_till_latency[index_inst1]);
			}
		}
	}
	//for PHI and load/write instructions, we add "manual dependence" when considering relationship between blocks. Here, we just consider load due to our needs.
	if(isa<LoadInst>(inst)){
		if(instr_index_loop.count(inst)){
			unsigned inst_index=instr_index_loop[inst];
			for(std::map<Instruction*,unsigned>::iterator it=instr_index_loop.begin();it!=instr_index_loop.end();++it){
				unsigned inst_tmp_index=it->second;
				for(std::vector< std::pair<int,float> >::iterator i= dependence_loop[inst_tmp_index].begin(); i!=dependence_loop[inst_tmp_index].end(); ++i){
					if(i->first==(int)inst_index){
						float latency_tmp=loop_inst_till_latency[inst_tmp_index];

	}
	return round(latency);
}

float find_latency_after_inst(Instruction *inst, std::map<Instruction*, unsigned> &instr_index_loop, std::vector<std::pair<int,float> > *dependence_loop)
{
	float latency=0.0;
	std::map<unsigned, float> loop_inst_till_latency;
	float cp=loop_solveCP(dependence_loop,loop_inst_till_latency);
	if(cp<0.0){
		errs()<<"Please check2...\n";
	}
	unsigned index_inst=instr_index_loop[inst];
	latency=loop_inst_till_latency[index_inst];
	return round(latency);
}

float find_latency(Instruction *inst_begin, Instruction *inst_end, std::map<Instruction*, unsigned> &instr_index_loop, std::vector<std::pair<int,float> > *dependence_loop)
{
	std::queue<int> index_queue[INSN_NUM];
	float latency=0.0;
	unsigned index_inst_begin=instr_index_loop[inst_begin];
	unsigned index_inst_end=instr_index_loop[inst_end];
	float *T=new float [INSN_NUM];
	for (int i=0;i<INSN_NUM;++i)
		T[i]=0.0;
	std::map<int,int> pushed_inst;
	if(!dependence_loop[index_inst_begin].empty())
	{
		for(std::vector< std::pair<int,float> >::iterator i= dependence_loop[index_inst_begin].begin(); i!=dependence_loop[index_inst_begin].end(); ++i)
		{
			if(i->first==(int)index_inst_begin)
			{
				if(isa<LoadInst>(inst_begin)){
					latency = 2.0;
				}
				else if(PHINode *phi=dyn_cast<PHINode>(inst_begin)){
					for(unsigned ii=0,ie=phi->getNumIncomingValues();ii!=ie;ii++){
						Value *incoming=phi->getIncomingValue(ii);
						if(isa<LoadInst>(incoming)){
							latency = 2.0;
							break;
						}
						else{
							latency = std::max(i->second, latency);
						}
					}
				}
				else{
					latency = std::max(i->second, latency);
				}
				T[index_inst_begin]=latency;
				continue;
			}
			else{
				if(!pushed_inst.count((int)index_inst_begin)){
					int index_tmp=i->first;
					float latency_tmp=i->second;
					T[index_tmp]=T[index_inst_begin]+latency_tmp;
					index_queue[index_inst_begin].push(index_tmp);
					pushed_inst[index_inst_begin]=1;
				}
				else{
					int index_tmp=i->first;
					float latency_tmp=i->second;
					float T_tmp=T[index_inst_begin]+latency_tmp;
					T[index_tmp]=std::max(T_tmp,T[index_tmp]);
				}
			}
		}
	}
	for(int j=index_inst_begin; j<= (int)index_inst_end; ++j)
	{
		while(!index_queue[j].empty())
		{
			int index_test=index_queue[j].front();
			index_queue[j].pop();
			for(std::vector< std::pair<int,float> >::iterator i= dependence_loop[index_test].begin(); i!=dependence_loop[index_test].end(); ++i)
			{
				float tmp=0.0;
				if(i->first==index_test){
					tmp = T[index_test] + i->second;
					T[index_test]=std::max(tmp,T[index_test]);
				}
				else{
					if(!pushed_inst.count(index_test)){
						int index_tmp=i->first;
						float latency_tmp=i->second;
						T[index_tmp]=T[index_test]+latency_tmp;
						index_queue[index_test].push(index_tmp);
					}
					else{
						int index_tmp=i->first;
						float latency_tmp=i->second;
						float T_tmp=T[index_test]+latency_tmp;
						T[index_tmp]=std::max(T_tmp,T[index_tmp]);
					}
				}
			}
		}
	}

	if(T[index_inst_end] == 0.0){
		//errs()<<"These two instruction have no dependency\n";
		latency=0.0;
	}
	else{
		latency=T[index_inst_end];
	}
	delete []T;
	return latency;
}

float get_inst_latency(Instruction *inst, std::map<Instruction*, unsigned> &instr_index_loop,std::vector<std::pair<int,float> > *dependence_loop)
{
	float latency=0.0;
	if(instr_index_loop.count(inst)){
		unsigned index=instr_index_loop[inst];
		for(std::vector<std::pair<int,float> >::iterator i=dependence_loop[index].begin();i!=dependence_loop[index].end();++i){
			if((i->first==(int)index)){
				latency=i->second;
			}
		}
	}
	return latency;
}

void set_inst_latency(Instruction *inst,float latency,std::map<Instruction*, unsigned> &instr_index_loop,std::vector<std::pair<int,float> > *dependence_loop)
{
	if(instr_index_loop.count(inst)){
		unsigned index=instr_index_loop[inst];
		for(std::vector<std::pair<int,float> >::iterator i=dependence_loop[index].begin();i!=dependence_loop[index].end();++i){
			if((i->first==(int)index)){
				i->second=latency;
			}
		}
	}
}

						latency=std::max(latency,latency_tmp);
					}
				}


/*
 *  COMBA
 *  Copyright (C) 2017  RCSL, HKUST
 *
 *  ONLY FOR ACADEMIC USE, NOT FOR COMMERCIAL USE.
 *
 *  Please use our tool at academic institutions and non-profit
 *  research organizations for research use.
 *
 *
 */

#include "InstLibrary.h"
#include "Library.h"
#include "Power.h"

float inst_latency_in_library(Instruction *inst)
{
	float latency=0.0;
	switch(inst->getOpcode()){
		//Terminator Instructions
		case Instruction::Ret:
		case Instruction::Br:
		case Instruction::Switch:
		case Instruction::IndirectBr:
		case Instruction::Invoke:
		case Instruction::Resume:
		case Instruction::Unreachable:
		  break;
		//Standard binary operators
		case Instruction::Add:
		case Instruction::Sub:
		  latency=INT_ADD;
		  break;
		case Instruction::FAdd:
		case Instruction::FSub:
		  latency=FP_ADD;
		  break;
		case Instruction::Mul:
		  Value *opr0;
		  opr0=inst->getOperand(1);
		  if(ConstantInt *cont=dyn_cast<ConstantInt>(opr0)){
			  int opconst=cont->getSExtValue();
			  if(is_power_of_two(opconst)){
				  latency=SHIFT;
				  //errs()<<"Ignored: multiplied by a contant which is power of two.\n";
			  }
			  else{
				  float SHL_ADD=0.0;
				  if(frequency==100){
					  SHL_ADD=SHIFT+INT_ADD;
				  }
				  else if(frequency==125){
					  SHL_ADD=SHIFT+INT_ADD;
				  }
				  else if(frequency==150){
					  SHL_ADD=SHIFT+INT_ADD;
				  }
				  else if(frequency==200){
					  SHL_ADD=SHIFT+INT_ADD;
				  }
				  else if(frequency==250){
					 SHL_ADD=SHIFT+INT_ADD+0.2;
				  }
				  int bound_power_two=closest_bound_power_two(opconst);
				  if(opconst<0){
					  opconst=-opconst;
				  }
				  int delta=opconst-bound_power_two;
				  if(delta<0){
					  delta=-delta;
				  }
				  if(delta==1){
					  latency=SHL_ADD;
				  }
				  else{
					  if(is_power_of_two(delta)){
						  latency=SHL_ADD;
					  }
					  else{
						  latency=INT_MULT;
					  }
				  }
			  }
		  }
		  else{
			  latency=IMULT;
		  }
		  break;
		case Instruction::FMul:
		  latency=FP_MULT;
		  break;
		case Instruction::SDiv:
		case Instruction::SRem:
		  Value *opr;
		  opr=inst->getOperand(1);

			  int opconst=cont->getSExtValue();
			  if(is_power_of_two(opconst)){
				  float SHL_DIV=0.0;
				  if(frequency==100){
					  SHL_DIV=SELECT_LATENCY+2*INT_ADD;
				  }
				  else if(frequency==125){
					  SHL_DIV=SELECT_LATENCY+2*INT_ADD;
				  }
				  else if(frequency==150){
					 SHL_DIV=SELECT_LATENCY+2*INT_ADD;
				  }
				  else if(frequency==200){
					 SHL_DIV=SELECT_LATENCY+INT_ADD;
				  }
				  else if(frequency==250){
					 SHL_DIV=SELECT_LATENCY+INT_ADD;
				  }
				  latency=SHL_DIV;
				  //errs()<<"Ignored: Divided by a contant which is power of two.\n";
			  }
			  else{
				  latency=INT_DIV;
			  }
		  }
		  else{
			  latency=IDIV;
		  }
		  break;
		case Instruction::UDiv:
		case Instruction::URem:
		  Value *opr1;
		  opr1=inst->getOperand(1);
		  if(ConstantInt *cont=dyn_cast<ConstantInt>(opr1)){
			  int opconst=cont->getSExtValue();
			  if(is_power_of_two(opconst)){
				  float SHL_DIV=0.0;
				  if(frequency==100){
					  SHL_DIV=SELECT_LATENCY+2*INT_ADD;
				  }
				  else if(frequency==125){
					  SHL_DIV=SELECT_LATENCY+2*INT_ADD;
				  }
				  else if(frequency==150){
					 SHL_DIV=SELECT_LATENCY+INT_ADD;
				  }
				  else if(frequency==200){
					 SHL_DIV=SELECT_LATENCY+INT_ADD;
				  }
				  else if(frequency==250){
					 SHL_DIV=SELECT_LATENCY+INT_ADD;
				  }
				  latency=SHL_DIV;
				  //errs()<<"Ignored: Divided by a contant which is power of two.\n";
			  }
			  else{
				  latency=U_DIV;
			  }
		  }
		  else{
			  latency=UDIV;
		  }
		  break;
		case Instruction::FDiv:
		case Instruction::FRem:
		 latency=FP_DIV;
		  break;

		//Logical operators (integer operands)
		case Instruction::Shl:
		case Instruction::LShr:
		case Instruction::AShr:
		 latency=SHIFT;
		  break;
		case Instruction::And:
		case Instruction::Or:
		case Instruction::Xor:
		 latency=INT_ADD;
		  break;
		//Memory operators
		case Instruction::Alloca:
		 latency=ALLOCA_LATENCY;
		 break;
		case Instruction::Load:
		case Instruction::Store:
		 break;

		case Instruction::GetElementPtr:
		 latency=GEP_LATENCY;
		 break;

		case Instruction::Fence: break;
		case Instruction::AtomicCmpXchg: break;
		case Instruction::AtomicRMW: break;

		//Cast operators
		case Instruction::Trunc:
		case Instruction::ZExt:
		case Instruction::SExt:
		case Instruction::FPTrunc:
		case Instruction::FPExt:
		 latency=0.0;
		 break;
		case Instruction::PtrToInt:
		case Instruction::IntToPtr:
		case Instruction::BitCast:
		 latency=CAST_LATENCY;
		 break;
		case Instruction::FPToUI:
		case Instruction::FPToSI:
		 latency=FP_TO_SI;
		 break;
		case Instruction::UIToFP:
		case Instruction::SIToFP:
		 latency=SI_TO_FP;
		 break;
		case Instruction::AddrSpaceCast:
		 errs()<<"In AddrSpaceCast, it's not normal.\n";
		 break;
		//Other operators
		case Instruction::ICmp:
		 latency=ICMP_LATENCY;
		  break;
		case Instruction::FCmp:
		 latency=FCMP_LATENCY;
		  break;
		case Instruction::PHI:
		 latency=PHI_LATENCY;
		  break;

		case Instruction::Call:
		  break;

		case Instruction::Select:
		 latency=SELECT_LATENCY;
		  break;

		case Instruction::UserOp1:
		case Instruction::UserOp2:
		case Instruction::VAArg:
		case Instruction::ExtractElement:
		case Instruction::InsertElement:
		case Instruction::ShuffleVector:
		case Instruction::ExtractValue:
		case Instruction::InsertValue:
		case Instruction::LandingPad:
		 break;

		default:
		 puts("It is something cannot be handled\n");
		 exit(0);
	}
	return latency;
}


/*
 *  COMBA
 *  Copyright (C) 2017  RCSL, HKUST
 *
 *  ONLY FOR ACADEMIC USE, NOT FOR COMMERCIAL USE.
 *
 *  Please use our tool at academic institutions and non-profit
 *  research organizations for research use.
 *
 *
 */

#include "LoopDepth.h"

int nested_loop_depth_for_loop(Loop* L)
{
	int counter_depth=0;
	std::vector<Loop*> subLoops=L->getSubLoops();
	int subLoop_num = subLoops.size();
	if(subLoops.empty()){
		counter_depth += 1;
	}
	else{
		int counter=0;
		for(int j=0;j<subLoop_num;j++)

		{
			int counter_tmp=0;
			Loop* sub_loop=subLoops.at(j);
			counter_tmp = nested_loop_depth_for_loop(sub_loop);
			counter=std::max(counter,counter_tmp);


/*
 *  COMBA
 *  Copyright (C) 2017  RCSL, HKUST
 *
 *  ONLY FOR ACADEMIC USE, NOT FOR COMMERCIAL USE.
 *
 *  Please use our tool at academic institutions and non-profit
 *  research organizations for research use.
 *
 *
 */

#include "LoopLatency.h"
#include "Constant.h"
#include "ArrayName.h"
#include "ComputeCriticalPath.h"
#include "FindLatency.h"
#include "Reschedule.h"
#include "Loop_II.h"
#include "UpdateMem.h"
#include "ComputeFixedPath.h"
#include "Library.h"

bool test_loop_is_perfect(Loop* L)
{
	bool loop_is_perfect=false;
	std::vector<Loop*> subLoops=L->getSubLoops();
	int size = subLoops.size();
	if(subLoops.empty())
	{
		Instruction *indvar=NULL;
		Loop* Loop_parent=L->getParentLoop();
		BasicBlock *head=Loop_parent->getHeader();
		BasicBlock *latch=Loop_parent->getLoopLatch();
		TerminatorInst *last_inst=latch->getTerminator();
		BranchInst *br_inst=dyn_cast<BranchInst>(last_inst);
		CmpInst *cmp=dyn_cast<CmpInst>(br_inst->getCondition());
		Value *cmpOp0=cmp->getOperand(0);
		Value *cmpOp1=cmp->getOperand(1);
		if(Instruction *cmp_op0=dyn_cast<Instruction>(cmpOp0))
		{
			indvar=cmp_op0;
		}
		else if(Instruction *cmp_op1=dyn_cast<Instruction>(cmpOp1))
		{
			indvar=cmp_op1;
		}
		else{
			errs()<<"Not a common conditional inst.\n";
		}
		for(auto iti=latch->begin();iti!=latch->end();++iti)
		{
		   Instruction *inst=iti;
		   if((inst==cmp)||(inst==br_inst)||(inst==indvar))
		   {
			   loop_is_perfect=true;
		   }
		   else{
			   loop_is_perfect=false;
			   break;
		   }
		}
		if(loop_is_perfect==true){
			   for(auto ii=head->begin();ii !=head->end();++ii)
			   {
				   Instruction *inst_h=ii;
				   if(isa<StoreInst>(inst_h)){
					   Value *array=get_array_name(inst_h);
					   for(Loop::block_iterator BI=L->block_begin(), BE=L->block_end(); BI !=BE; ++BI){
						   BasicBlock *bb=*BI;
						   for(auto it=bb->begin();it!=bb->end();++it){
							   if(isa<LoadInst>(it)){
								   Value *array_name=get_array_name(it);
								   if(array_name==array){
									   loop_is_perfect=false;
								   }
							   }
						   }
					   }
				   }
			   }
		}
	}
	else if(size==1){
		Loop* L1 = subLoops.at(0);
		if(Loops_pipeline[L1]==1){
			Instruction *indvar=NULL;
			Loop* Loop_parent=L1->getParentLoop();
			BasicBlock *head=Loop_parent->getHeader();
			BasicBlock *latch=Loop_parent->getLoopLatch();
			TerminatorInst *last_inst=latch->getTerminator();
			BranchInst *br_inst=dyn_cast<BranchInst>(last_inst);
			CmpInst *cmp=dyn_cast<CmpInst>(br_inst->getCondition());
			Value *cmpOp0=cmp->getOperand(0);
			Value *cmpOp1=cmp->getOperand(1);
			if(Instruction *cmp_op0=dyn_cast<Instruction>(cmpOp0))
			{
				indvar=cmp_op0;
			}
			else if(Instruction *cmp_op1=dyn_cast<Instruction>(cmpOp1))
			{
				indvar=cmp_op1;
			}
			else{
				errs()<<"Not a common conditional inst.\n";
			}

			for(auto iti=latch->begin();iti!=latch->end();++iti)
			{
			   Instruction *inst=iti;
			   if((inst==cmp)||(inst==br_inst)||(inst==indvar))
			   {
				   loop_is_perfect=true;
			   }
			   else{
				   loop_is_perfect=false;
				   break;
			   }
			}
			if(loop_is_perfect==true){
			   for(auto ii=head->begin();ii !=head->end();++ii)
			   {
				   Instruction *inst_h=ii;
				   if(isa<StoreInst>(inst_h)){
					   Value *array=get_array_name(inst_h);
					   for(Loop::block_iterator BI=L1->block_begin(), BE=L1->block_end(); BI!=BE; ++BI){
						   BasicBlock *bb=*BI;
						   for(auto it=bb->begin();it!=bb->end();++it){
							   if(isa<LoadInst>(it)){
								   Value *array_name=get_array_name(it);
								   if(array_name==array){
									   loop_is_perfect=false;
								   }
							   }
						   }
					   }
				   }
			   }
			}
		}
		else{
			loop_is_perfect=test_loop_is_perfect(L1);
		}
	}
	else{
		loop_is_perfect=false;
	}
	return loop_is_perfect;
}

float pipeline_iteration_latency(LoopInfo* LI, Loop *L,int II, std::map<Instruction*, unsigned> &instr_index_loop, std::map<Instruction*, unsigned> &load_order_buff, std::vector<std::pair<int,float> > *dependence_loop)
{
	float iteration_latency=0.0;
	iteration_latency=round(update_loopCP(LI,L,load_order_buff));
	std::map<Value*,int> array_read;
	std::map<Value*,int> array_write;
	for(std::map<Instruction*,unsigned>::iterator it=instr_index_loop.begin();it!=instr_index_loop.end();++it)
	{
		Instruction *inst=it->first;
		if(isa<LoadInst>(inst)){
			Value *array_name=get_array_name(inst);
			if(!array_read.count(array_name)){
				if(array_number.count(array_name)){
					array_read[array_name]=array_number[array_name];
				}
			}
		}
		else if(isa<StoreInst>(inst)){
			Value *array_name=get_array_name(inst);
			if(!array_write.count(array_name)){
				if(array_number.count(array_name)){
					array_write[array_name]=array_number[array_name];
				}
			}
		}
	}
	float IL_delta=0.0;
	for(std::map<Value*,int>::iterator it=array_read.begin();it!=array_read.end();++it)
	{
		Value *array=it->first;
		if(array_write.count(array)){
			float latency_min_write=inf;
			float latency_max_write=0.0;
			float latency_max_read=0.0;
			for(std::map<Instruction*,unsigned>::iterator ii=instr_index_loop.begin();ii!=instr_index_loop.end();++ii){
				Instruction *inst=ii->first;
				if(isa<StoreInst>(inst)){
					Value *store_array=get_array_name(inst);
					if(array==store_array){
						float latency_tmp=find_latency_after_inst(inst,instr_index_loop,dependence_loop);
						latency_min_write=std::min(latency_min_write,latency_tmp);
						latency_max_write=std::max(latency_max_write,latency_tmp);
					}
				}
				else if(isa<LoadInst>(inst)){
					Value *load_array=get_array_name(inst);
					if(load_array==array){
						float latency_tmp1=find_latency_after_inst(inst,instr_index_loop,dependence_loop);
						latency_max_read=std::max(latency_max_read,latency_tmp1);
					}
				}
			}
			latency_min_write=round(latency_min_write)-1.0;
			latency_max_write=round(latency_max_write)-1.0;
			latency_max_read=round(latency_max_read);
			int divider=(int)ceil(latency_max_write/(float)II);
			int upper_bound=divider*II;
			IL_delta=std::max((float)upper_bound,IL_delta);
		}
	}
	if(IL_delta>=iteration_latency){
		iteration_latency = IL_delta;//+= IL_delta;
	}
	return round(iteration_latency);
}

float perfect_pipeline_iteration_latency(LoopInfo* LI, Loop *L, int II, std::map<Instruction*, unsigned> &instr_index_loop,std::map<Instruction*, unsigned> &load_order_buff, std::vector<std::pair<int,float> > *dependence_loop)
{
	float usual_latency=pipeline_iteration_latency(LI,L,II,instr_index_loop,load_order_buff,dependence_loop);
	float iteration_latency=round(update_loopCP(LI,L,load_order_buff));
	Loop *parent=L->getParentLoop();
	float new_iteration_latency=round(update_loopCP(LI,parent,load_order_buff));
	if(new_iteration_latency>iteration_latency){
		usual_latency += new_iteration_latency-iteration_latency;
	}
	return usual_latency;
}

float compute_cycles_in_loop(LoopInfo* LI, Loop* L, std::map<Instruction*, unsigned> &load_order_buff)
{
	//float constant=1.0;
	int II=1;
	std::vector<Loop*> subLoops = L->getSubLoops();
	int subLoop_num = subLoops.size();
	float tmp_sum = 0.0;

	if ( subLoops.empty() )
	{
		if(Loops_pipeline[L]==1)
		{
			std::map<Instruction*, unsigned> instr_index_loop;
			std::vector<std::pair<int,float> > dependence_loop[INSN_NUM];
			std::map<Instruction*,std::queue<unsigned> > inst_map_branch;
			dependence_set(LI,L,instr_index_loop,load_order_buff,dependence_loop,inst_map_branch);
			reschedule_dependence(LI,frequency,instr_index_loop,dependence_loop);
			II=compute_II(LI,L,instr_index_loop,load_order_buff,dependence_loop);
			float pipeline_latency=pipeline_iteration_latency(LI,L,II,instr_index_loop,load_order_buff,dependence_loop);
			tmp_sum += pipeline_latency + II*(ceil((float)Loops_counter[L]/(float)Loops_unroll[L])-1);
		}
		else if(Loops_pipeline[L]==0){
			if(Loops_unroll[L]>=Loops_counter[L]){
				errs()<<"Single Loop "<<L<<" is unrolled completely.\n";
			}
			else{
				float update_loop_cp=round(update_loopCP(LI,L,load_order_buff));
				tmp_sum += update_loop_cp * ceil((float)Loops_counter[L]/(float)Loops_unroll[L]);
			}
		}
		else{
			errs()<<"Loop pipelining is not set correctly\n";
		}
		//errs()<<"subLoop: "<<L<<"\n";
		//errs()<<"Loop cycles: "<<tmp_sum<<"\n";
		loop_cycles[L]=tmp_sum;
		//tmp_sum += constant;
	}
	else
	{
		bool loop_is_perfect=test_loop_is_perfect(L);
		if(loop_is_perfect==false){
			//errs()<<"Imperfect Loop: May change into perfect loop (loop merge or add if-else within inner loop) to improve performance.\n";
		}
		if(Loops_pipeline[L]==1){
			std::map<Instruction*, unsigned> instr_index_loop;
			std::vector<std::pair<int,float> > dependence_loop[INSN_NUM];
			std::map<Instruction*,std::queue<unsigned> > inst_map_branch;
			dependence_set(LI,L,instr_index_loop,load_order_buff,dependence_loop,inst_map_branch);
			reschedule_dependence(LI,frequency,instr_index_loop,dependence_loop);
			II=compute_II(LI,L,instr_index_loop,load_order_buff,dependence_loop);
			float pipeline_latency=pipeline_iteration_latency(LI,L,II,instr_index_loop,load_order_buff,dependence_loop);
			//errs()<<"Pipelined latency: "<<pipeline_latency<<"\n";
			//errs()<<II<<"\n";
			tmp_sum += pipeline_latency + II*(ceil((float)Loops_counter[L]/(float)Loops_unroll[L])-1);
		}
		else if(Loops_pipeline[L]==0){
			Loop* sub_loop_first=subLoops.at(0);
			if((loop_is_perfect==true) && (Loops_unroll[L]==1) && (Loops_pipeline[sub_loop_first]==1)){
				std::map<Instruction*, unsigned> instr_index_subloop;
				std::vector<std::pair<int,float> > dependence_subloop[INSN_NUM];
				std::map<Instruction*,std::queue<unsigned> > inst_map_branch;
				dependence_set(LI,sub_loop_first,instr_index_subloop,load_order_buff,dependence_subloop,inst_map_branch);
				reschedule_dependence(LI,frequency,instr_index_subloop,dependence_subloop);
				int II_subloop=compute_II(LI,sub_loop_first,instr_index_subloop,load_order_buff,dependence_subloop);
				float subloop_cp=perfect_pipeline_iteration_latency(LI,sub_loop_first,II_subloop,instr_index_subloop,load_order_buff,dependence_subloop);
				//errs()<<"Pipelined latency: "<<subloop_cp<<"\n";
				//errs()<<II_subloop<<"\n";
				tmp_sum += subloop_cp + II_subloop*((float)Loops_counter[L]/(float)Loops_unroll[L] * ceil((float)Loops_counter[sub_loop_first]/(float)Loops_unroll[sub_loop_first])-1);
			}
			else{
				if(Loops_unroll[L]>=Loops_counter[L]){
					errs()<<"Loop "<<L<<" is unrolled completely and just to calculate its subloops.\n";
				}
				else{
					std::map<Instruction*, unsigned> instr_index_loop;
					std::vector<std::pair<int,float> > dependence_loop[INSN_NUM];
					std::map<Instruction*,std::queue<unsigned> > inst_map_branch;
					std::map<unsigned, float> loop_inst_till_latency;
					std::map<Loop*,int> updated_loop;
					unsigned index_loop=1;
					for (int i=0;i<INSN_NUM;++i)
					{
						std::vector<std::pair<int,float> > vec;
						dependence_loop[i].swap(vec);
					}
					for(Loop::block_iterator BI=L->block_begin(), BE=L->block_end(); BI !=BE; ++BI)
					{
						BasicBlock *bb=*BI;
						bool bb_in_loop_flag=false;
						Loop *subloop=NULL;
						for(int j=0; j<subLoop_num; j++){
							Loop* sub_loop = subLoops.at(j);
							if(sub_loop->contains(bb)){
								bb_in_loop_flag = true;
								subloop=sub_loop;
								break;
							}
						}
						if(bb_in_loop_flag==false){
							for(auto iti=bb->begin();iti!=bb->end();++iti){
								update(LI,iti,index_loop,instr_index_loop,load_order_buff,dependence_loop,inst_map_branch);
								if(isa<LoadInst>(iti)){
									update_load(LI,iti,instr_index_loop,dependence_loop);
								}
								else if(isa<StoreInst>(iti)){
									update_store(LI,iti,instr_index_loop,dependence_loop);
								}
							}
						}
						else{
							 if(updated_loop.count(subloop)){
								 ++updated_loop[subloop];
							 }
							 else{
								 updated_loop[subloop]=1;
								 Loop_insert(LI, subloop, index_loop, instr_index_loop,load_order_buff, dependence_loop,inst_map_branch);
							 }
						}
					}
					reschedule_dependence(LI,frequency,instr_index_loop,dependence_loop);
					float critical_path_loop = loop_solveCP(dependence_loop,loop_inst_till_latency);
					tmp_sum = round(critical_path_loop) * ceil((float)Loops_counter[L]/(float)Loops_unroll[L]);
				}
			}
		}
		else{
			errs()<<"Pipeline directive is not set correctly.\n";
		}
		loop_cycles[L]=tmp_sum;
		//errs()<<"Loop: "<<L<<"\n";
		//errs()<<"Loop cycles: "<<tmp_sum<<"\n";
	}
	return tmp_sum;
}

//insert loop (not unrolled completely) as a "big" node in the critical path
void Loop_insert(LoopInfo* LI, Loop *L, unsigned &index_loop, std::map<Instruction*, unsigned> &instr_index_loop, std::map<Instruction*, unsigned> &load_order_buff, std::vector<std::pair<int,float> > *dependence_loop, std::map<Instruction*,std::queue<unsigned> > &inst_map_branch)
{
	 std::map<Loop*,int> updated_loop;
	 std::vector<Loop*> subLoops=L->getSubLoops();
	 int subLoop_num = subLoops.size();
	 if(subLoops.empty()){
		 if(Loops_unroll[L]>=Loops_counter[L]){
			 for(Loop::block_iterator BI=L->block_begin(), BE=L->block_end(); BI !=BE; ++BI){
				 BasicBlock *bb=*BI;
				 for(auto iti=bb->begin();iti!=bb->end();++iti)
				 {
				 	update(LI,iti,index_loop,instr_index_loop,load_order_buff,dependence_loop,inst_map_branch);
					if(isa<LoadInst>(iti)){
						update_load(LI,iti,instr_index_loop,dependence_loop);
					}
					else if(isa<StoreInst>(iti)){
						update_store(LI,iti,instr_index_loop,dependence_loop);
					}
				 }
			 }
		 }
		 else{
			 Loop *parent=L->getParentLoop();
			 int factor=1;
			 if(parent!=NULL){
				 factor=Loops_unroll[parent];
			 }
			 float latency=compute_cycles_in_loop(LI,L,load_order_buff)*factor;
			 loop_cycles[L]=latency;
			 for(Loop::block_iterator BI=L->block_begin(), BE=L->block_end(); BI !=BE; ++BI)
			 {
				 BasicBlock *bb=*BI;
				 for(auto iti=bb->begin();iti!=bb->end();++iti)
				 {
					 update_fix_latency(L,iti,latency,index_loop,instr_index_loop,dependence_loop);
					 inst_reschedule_considered[iti]=1;
				 }
			 }
		 }
	 }
	 else{
		 if(Loops_unroll[L]>=Loops_counter[L]){
			 for(Loop::block_iterator BI=L->block_begin();BI != L->block_end(); ++BI){
				 BasicBlock *bb=*BI;
				 Loop *subloop=NULL;
				 bool bb_in_loop_flag=false;
				 for(int j=0; j<subLoop_num; j++){
					 Loop* sub_loop = subLoops.at(j);
					 if(sub_loop->contains(bb)){
						 bb_in_loop_flag = true;
						 subloop=sub_loop;
						 break;
					 }
				 }
				 if(bb_in_loop_flag==false){
					 for(auto iti=bb->begin();iti!=bb->end();++iti){
						 update(LI,iti,index_loop,instr_index_loop,load_order_buff,dependence_loop,inst_map_branch);
						 if(isa<LoadInst>(iti)){
							 update_load(LI,iti,instr_index_loop,dependence_loop);
			 			 }
						 else if(isa<StoreInst>(iti)){
							 update_store(LI,iti,instr_index_loop,dependence_loop);
						 }
					 }
				 }
				 else{
					 if(updated_loop.count(subloop)){
						 ++updated_loop[subloop];
					 }
					 else{
						 updated_loop[subloop]=1;
						 Loop_insert(LI, subloop, index_loop, instr_index_loop,load_order_buff,dependence_loop,inst_map_branch);
					 }
				 }
			 }
		 }
		 else{
			 Loop *parent=L->getParentLoop();
			 int factor=1;
			 if(parent!=NULL){
				 factor=Loops_unroll[parent];
			 }
			 float latency=compute_cycles_in_loop(LI,L,load_order_buff)*factor;
			 loop_cycles[L]=latency;
			 for(Loop::block_iterator BI=L->block_begin();BI != L->block_end(); ++BI){
				 BasicBlock *bb=*BI;
				 for(auto iti=bb->begin();iti!=bb->end();++iti)
				 {
					 update_fix_latency(L,iti,latency,index_loop,instr_index_loop,dependence_loop);

					 inst_reschedule_considered[iti]=1;
				 }
			 }


/*
 *  COMBA
 *  Copyright (C) 2017  RCSL, HKUST
 *
 *  ONLY FOR ACADEMIC USE, NOT FOR COMMERCIAL USE.
 *
 *  Please use our tool at academic institutions and non-profit
 *  research organizations for research use.
 *
 *
 */

#include "PartitionFactor.h"
#include "ArrayName.h"

int partition_factor(Instruction *inst)
{
	int factor=1;
	Value *array_name=get_array_name(inst);
	if(array_number.count(array_name)){
		int array_index=array_number[array_name];
		int dim=array_dimension[array_index];
		if(dim==1){

			factor=it->second;
		}
		else if(dim>1){
			int fac=1;
			for(std::vector< std::pair<int,int> >::iterator it= array_partition[array_index].begin(); it!=array_partition[array_index].end(); ++it){
				fac=it->second;
				factor *=fac;
			}
		}
		else{
			errs()<<"Dim is 0, wrong!\n";
		}
	}
	return factor;
}

std::vector< std::pair<int,int> >::iterator it = array_partition[array_index].begin();


/*
 *  COMBA
 *  Copyright (C) 2017  RCSL, HKUST
 *
 *  ONLY FOR ACADEMIC USE, NOT FOR COMMERCIAL USE.
 *
 *  Please use our tool at academic institutions and non-profit
 *  research organizations for research use.
 *
 *
 */

#include "Constant.h"
#include "Power.h"

bool is_power_of_two(int n)
{
	if(n==0)
		return false;
	else if(n==1)
		return true;
	else if(n%2)
		return false;
	else
		return is_power_of_two(n/2);
}

int closest_bound_power_two(int n)
{
	int m1=0;
	int m2=0;
	int m=0;
	if(n<0){
		n=-n;
	}

		bool is_power_two=is_power_of_two(i);
		if(is_power_two==true){
			m1=i;
			break;
		}
	}
	for(int i=n;i<inf;++i){
		bool is_power_two=is_power_of_two(i);
		if(is_power_two==true){
			m2=i;
			break;
		}
	}
	int delta_m1=n-m1;
	int delta_m2=m2-n;
	if(delta_m1>delta_m2){
		m=m2;
	}
	else{
		m=m1;
	}
	return m;
}

int get_power_of_two(int n)
{
	int power=1;
	if(n==1){
		power=0;
	}
	else if(is_power_of_two(n)==true){
		power+=get_power_of_two(n/2);
	}
	else{
		n--;
		power=get_power_of_two(n);
	}
	return power;
}


/*
 *  COMBA
 *  Copyright (C) 2017  RCSL, HKUST
 *
 *  ONLY FOR ACADEMIC USE, NOT FOR COMMERCIAL USE.
 *
 *  Please use our tool at academic institutions and non-profit
 *  research organizations for research use.
 *
 *
 */

#include "SetPragma.h"

void set_dataflow(Module &M)
{
	for(auto F=M.begin(), E=M.end(); F!=E; ++F){
		unsigned FnID=F->getIntrinsicID();
		if(FnID==0){
			if(function_is_inline(F)==false){
				unsigned F_index=Function_index[F];
				Function_dataflow[F]=dataflow_input[F_index];
			}
		}
		/*else{
			errs()<<"This is intrinsic function of llvm.\n";
		}*/
	}
	for(auto F=M.begin(), E=M.end(); F!=E; ++F){
		unsigned FnID=F->getIntrinsicID();
		if(FnID==0){
			if(function_is_inline(F)==false){
				for(inst_iterator I=inst_begin(F), IE=inst_end(F); I!=IE; ++I){
					Instruction *inst= &*I;
					if(CallInst *call=dyn_cast<CallInst>(inst)){
						Function *callee=call->getCalledFunction();
						if(Function_dataflow.count(callee)){
							Function_dataflow[callee]=0;
							unsigned callee_index=Function_index[callee];
							dataflow_input[callee_index]=0;
							//StringRef FunName=callee->getName();
							//errs()<<FunName<<": Subfunctions cannot dataflow.\n";
						}
					}
				}
			}

		}
	}
}

bool function_is_inline(Function *F)
{
	 bool inline_function=false;
	 inline_function=F->hasFnAttribute(Attribute::AlwaysInline);
	 return inline_function;
}

bool function_is_noinline(Function *F)
{
	 bool noinline_function=false;
	 noinline_function=F->hasFnAttribute(Attribute::NoInline);
	 return noinline_function;
}

bool has_subFn(Function *F)
{
	bool Fn_has_subFn=false;
	for(inst_iterator I=inst_begin(F), IE=inst_end(F); I!=IE; ++I){
		Instruction *inst= &*I;
		if(CallInst *call=dyn_cast<CallInst>(inst)){
			Function *callee=call->getCalledFunction();
			unsigned FnID=callee->getIntrinsicID();
			if(FnID==0){
				Fn_has_subFn=true;
			}
		}
	}
	return Fn_has_subFn;
}

bool Fn_pipeline_modify(Function *F)
{
	unsigned FnID=F->getIntrinsicID();
	if(FnID==0){
		bool Fn_has_subFn=has_subFn(F);
		if(Fn_has_subFn==true){
			if(Function_pipeline[F]==1){
				for(inst_iterator I=inst_begin(F), IE=inst_end(F); I!=IE; ++I){
					Instruction *inst= &*I;
					if(CallInst *call=dyn_cast<CallInst>(inst)){
						Function *callee=call->getCalledFunction();
						if(Function_pipeline.count(callee)){
							Function_pipeline[callee]=1;
							unsigned callee_index=Function_index[callee];
							function_pipeline_input[callee_index]=1;
							//StringRef FunName=callee->getName();
							//errs()<<FunName<<": Subfunctions should be pipelined when top is pipelined.\n";
							bool sub_modify=Fn_pipeline_modify(callee);
							return sub_modify;
						}
					}
				}
			}
		}
	}
	return true;
}

void set_function_pipeline(Module &M)
{
	for(auto F=M.begin(), E=M.end(); F!=E; ++F){
		unsigned FnID=F->getIntrinsicID();
		if(FnID==0){
			if(function_is_inline(F)==false){
				unsigned F_index=Function_index[F];
				Function_pipeline[F]=function_pipeline_input[F_index];
			}
		}
		/*else{
			errs()<<"This is intrinsic function of llvm.\n";
		}*/
	}
	for(auto F=M.begin(), E=M.end(); F!=E; ++F){
		unsigned FnID=F->getIntrinsicID();
		if(FnID==0){
			if(function_is_inline(F)==false){
				if(Function_dataflow.count(F)){
					if(Function_dataflow[F]==1){
						Function_pipeline[F]=0;
						unsigned F_index=Function_index[F];
					    function_pipeline_input[F_index]=0;
						//StringRef FunName=F->getName();
						//errs()<<"Function pipeline is ignored when dataflow detected.\n";
						//errs()<<"Function: "<<FunName<<" should be top function.\n";
					}
				}
			}
		}
	}
	for(auto F=M.begin(), E=M.end(); F!=E; ++F){
		unsigned FnID=F->getIntrinsicID();
		if(FnID==0){
			if(function_is_inline(F)==false){
				if(Function_pipeline[F]==1){
					for(inst_iterator I=inst_begin(F), IE=inst_end(F); I!=IE; ++I){
						Instruction *inst= &*I;
						if(CallInst *call=dyn_cast<CallInst>(inst)){
							Function *callee=call->getCalledFunction();
							if(Function_pipeline.count(callee)){
								Function_pipeline[callee]=1;
								unsigned callee_index=Function_index[callee];
								function_pipeline_input[callee_index]=1;
								//StringRef FunName=callee->getName();
								//errs()<<"Function: "<<FunName<<": Subfunctions should be pipelined when top is pipelined.\n";
								bool sub_modify=Fn_pipeline_modify(callee);
								if(sub_modify==false){
									errs()<<"Pipeline of subfunction under function is not set correctly.\n";
								}
							}
						}
					}
				}
			}
		}
	}
}

bool set_subFn_pipeline(Loop *L)
{
	if(Loops_pipeline[L]==1){
		for(Loop::block_iterator BI=L->block_begin(), BE=L->block_end(); BI !=BE; ++BI){
				BasicBlock *bb=*BI;
				for(auto iti=bb->begin();iti!=bb->end();++iti)
				{
					if(CallInst *call=dyn_cast<CallInst>(iti)){
						Function *callee=call->getCalledFunction();
						if(Function_pipeline.count(callee)){
							Function_pipeline[callee]=1;
							unsigned callee_index=Function_index[callee];
							function_pipeline_input[callee_index]=1;
							//StringRef FunName=callee->getName();
							//errs()<<"Function: "<<FunName<<": is set to function pipelining due to its parent loop pipelining.\n";
							bool sub_mod=Fn_pipeline_modify(callee);
							if(sub_mod==false){
								errs()<<"Pipeline of subfunction under function is not set correctly.\n";
							}
						}
					}
				}
		}
	}
	else{
		std::vector<Loop*> subLoops = L->getSubLoops();
		if( !subLoops.empty() ){
            for (unsigned ii=0; ii<subLoops.size(); ii++)
			{
		        Loop* sub_loop = subLoops.at(ii);
		        bool subFn_pipeline=set_subFn_pipeline(sub_loop);
		        if(subFn_pipeline==false){
		        	errs()<<"Pipeline of subFunctions under loop is not set correctly.\n";
		        }
			}
	    }
	}
	return true;
}

void set_loop_counter_ul_pipeline(LoopInfo* LI, Function *F) //using queue to implement BFS.
{
	unsigned F_index=Function_index[F];
	int loop_index=0;
	for(int i=0;i<(int)F_index;++i){
		loop_index+=function_loop_num[i];
	}
	std::map<Loop*,int> loop_map_index;
	std::queue<Loop*> loop_queue;
	for (LoopInfo::reverse_iterator i = LI->rbegin(), e = LI->rend(); i != e; ++i){
        Loop *L = *i;
		Loops_counter[L] = Loops_counter_input[loop_index];
		Loops_unroll[L] = Loops_unroll_input[loop_index];
		Loops_pipeline[L] = Loops_pipeline_input[loop_index];
		loop_map_index[L]=loop_index;
        loop_index++;
		std::vector<Loop*> subLoops = L->getSubLoops();
		if(!subLoops.empty()){
			for (unsigned ii=0; ii<subLoops.size(); ii++){
				Loop* sub_loop = subLoops.at(ii);
				loop_queue.push(sub_loop);
			}
		}
	}
	while (!loop_queue.empty())
	{
        Loop* loop_test = loop_queue.front();
      	loop_queue.pop();
      	Loops_counter[loop_test] = Loops_counter_input[loop_index];
      	Loops_unroll[loop_test] = Loops_unroll_input[loop_index];
      	Loops_pipeline[loop_test] = Loops_pipeline_input[loop_index];
      	loop_map_index[loop_test]=loop_index;
      	loop_index++;
        std::vector<Loop*> subLoops = loop_test->getSubLoops();
      	for (unsigned ii=0; ii<subLoops.size(); ii++){
      		 Loop* sub_loop = subLoops.at(ii);
      		 loop_queue.push(sub_loop);
      	}
	}
	//Function/loop pipeline influence sub-loops pipelining
	if(Function_pipeline[F]==0){
		for(LoopInfo::reverse_iterator i = LI->rbegin(), e = LI->rend(); i!=e; ++i){
			Loop *L=*i;
			if(Loops_unroll[L]>=Loops_counter[L]){
				Loops_pipeline[L]=0;  //loop pipeline will be ignored if it is unrolled completely.
				int l_index=loop_map_index[L];
				Loops_pipeline_input[l_index]=0;
			}
			std::vector<Loop*> subloops=L->getSubLoops();
			if(!subloops.empty()){
				for(unsigned ii=0; ii< subloops.size(); ii++){
					Loop* sub_loop=subloops.at(ii);
					loop_queue.push(sub_loop);
				}
				while(!loop_queue.empty())
				{
					Loop* sub_loop_tmp=loop_queue.front();
					loop_queue.pop();
					int test_pipeline=0;
					Loop *loop_parent=sub_loop_tmp->getParentLoop();
					while(loop_parent != NULL){
						if(Loops_pipeline[loop_parent]==1){
							test_pipeline++;
						}
						loop_parent=loop_parent->getParentLoop();
					}
					if(test_pipeline>=1)
					{
						Loops_unroll[sub_loop_tmp]=Loops_counter[sub_loop_tmp];
						Loops_pipeline[sub_loop_tmp]=0;
						int sub_l_index=loop_map_index[sub_loop_tmp];
						Loops_unroll_input[sub_l_index]=Loops_unroll[sub_loop_tmp];
						Loops_pipeline_input[sub_l_index]=0;
        		    	  //errs()<<"SubLoop: "<<sub_loop_tmp<<" is unrolled completely due to loop pipeling.\n";
					}
					else if(test_pipeline==0)
					{
        		    	  //Loops_unroll[sub_loop_tmp]=Loops_unroll[sub_loop_tmp];
        		    	  //errs()<<"SubLoop: "<<sub_loop_tmp<<" doesn't change its directives setting.\n";
					}
					else errs()<<"It may be wrong when setting unrolling factor when pipelining\n";
					if(Loops_unroll[sub_loop_tmp]>=Loops_counter[sub_loop_tmp]){
						Loops_pipeline[sub_loop_tmp]=0;
						int sub_l_index=loop_map_index[sub_loop_tmp];
						Loops_pipeline_input[sub_l_index]=0;
					}
					std::vector<Loop*> subLoops_tmp = sub_loop_tmp->getSubLoops();
					for (unsigned ii=0; ii<subLoops_tmp.size(); ii++)
					{
						Loop* sub_loop = subLoops_tmp.at(ii);
						loop_queue.push(sub_loop);
					}
				}
			}
		}
	}
	else if(Function_pipeline[F]==1){
		for(LoopInfo::reverse_iterator i = LI->rbegin(), e = LI->rend(); i!=e; ++i){
			Loop *L=*i;
			Loops_unroll[L]=Loops_counter[L];
			Loops_pipeline[L]=0;
			int l_index=loop_map_index[L];
			Loops_unroll_input[l_index]=Loops_unroll[L];
			Loops_pipeline_input[l_index]=0;
			//errs()<<"Loop: "<<L<<" is unrolled completely due to function pipeling.\n";
			std::vector<Loop*> subloops=L->getSubLoops();
			if(!subloops.empty())
			{
				for(unsigned ii=0; ii< subloops.size(); ii++)
				{
					Loop* sub_loop=subloops.at(ii);
					//errs()<<"Loop: "<<sub_loop<<" is unrolled completely due to function pipeling.\n";
					loop_queue.push(sub_loop);
				}
			}
		}
		while(!loop_queue.empty()){
			Loop *loop_test=loop_queue.front();
			loop_queue.pop();
			Loops_unroll[loop_test]=Loops_counter[loop_test];
			Loops_pipeline[loop_test]=0;
			int ltest_index=loop_map_index[loop_test];
			Loops_unroll_input[ltest_index]=Loops_unroll[loop_test];
			Loops_pipeline_input[ltest_index]=0;
			std::vector<Loop*> subLoops = loop_test->getSubLoops();
			for (unsigned ii=0; ii<subLoops.size(); ii++)
			{
				Loop* sub_loop = subLoops.at(ii);
				loop_queue.push(sub_loop);
			}
		}
	}
	else{
		errs()<<"Function pipeline is not set correctly.\n";
	}
	//Loop pipeline influence sub-function pipelining
	for (LoopInfo::reverse_iterator i = LI->rbegin(), e = LI->rend(); i != e; ++i)
	{
		Loop *L = *i;
        bool set_subFn=set_subFn_pipeline(L);

void set_array_partition(Module &M)
{
	int size=array_number.size();
	int accumulated_dim=0;
	for(int i=0;i<size;++i){
		for(std::map<Value*,int>::iterator it=array_number.begin();it!=array_number.end();++it){
			if(it->second==i){
				int array_index=it->second;
				int dim=array_dimension[array_index];
				for(int j=0; j<dim;++j){
					int new_j = j+accumulated_dim;
					array_partition[array_index].push_back(std::make_pair(array_partition_type[new_j],array_partition_factor[new_j]));
					//errs()<<"array_index: "<<array_index<<" and type: "<<array_partition_type[new_j]<<" and factor "<<array_partition_factor[new_j]<<"\n";
				}
				accumulated_dim += dim;
			}
		}
	}
	std::map<Function*,int> Function_array_set;
	int order=0;
	for(auto F=M.begin(), E=M.end(); F!=E; ++F){
		unsigned FnID=F->getIntrinsicID();
		if(FnID==0){
			if(function_is_inline(F)==false){
				if(!Function_array_set.count(F)){
					Function_array_set[F]=order;
					order++;
				}
				for(inst_iterator I=inst_begin(F), E=inst_end(F); I != E; ++I){
					Instruction *inst=&*I;
					if(CallInst *call=dyn_cast<CallInst>(inst)){
						Function *callee=call->getCalledFunction();
						unsigned Fnid=callee->getIntrinsicID();
						if(Fnid==0){
							if(Function_array_set.count(callee)){
								if(callee!=F){
									keep_array_consistent(callee,call);
								}
							}
						}
					}
				}
			}
		}
	}
}

void keep_array_consistent(Function *callee, CallInst *call)
{
	int i=0;
	for(Function::arg_iterator it=callee->arg_begin();it!=callee->arg_end();++it){
		Value *arg=dyn_cast<Value>(it);
		Value *op=call->getArgOperand(i);
		Type *op_type=op->getType();
		bool is_array = op_type->isPointerTy()||op_type->isArrayTy();
		Value *array_op=NULL;
		if(is_array==true){
			if(!isa<Instruction>(op)){
				array_op=op;
			}
			else{
				if(GetElementPtrInst *gep=dyn_cast<GetElementPtrInst>(op)){
					Value *gep_pointer=gep->getPointerOperand();
					array_op=gep_pointer;
				}
			}
		}
		if(array_op!=NULL&&arg!=NULL){
			if(array_number.count(array_op)){
				if(array_number.count(arg)){
					int arg_index=array_number[arg];
					int op_index=array_number[array_op];
					int arg_dim=array_dimension[arg_index];
					int op_dim=array_dimension[op_index];
					if(op_dim!=arg_dim){
						errs()<<"ERROR: Formal argument is different with actual argument !\n";
					}
					else{
						array_partition[arg_index]=array_partition[op_index];
						//errs()<<arg_index<<" and "<<op_index<<"\n";
						//errs()<<"Formal argument is set again due to calling function.\n";
					}
				}
			}
			else{
				if(array_op->getValueID()==Value::GlobalVariableVal){
					if(array_number.count(arg)){
						int arg_index=array_number[arg];
						//int arg_dim=array_dimension[arg_index];
						if(array_partition[arg_index].empty()){
							errs()<<"Check: empty come here...\n";
						}
						if(!array_partition[arg_index].empty()){
							for(std::vector<std::pair<int,int> >::iterator ii=array_partition[arg_index].begin();ii!=array_partition[arg_index].end();++ii){
								ii->second=1;
								//errs()<<"Formal argument is set to factor 1 due to global variable using.\n";
							}
						}

					}
				}
			}
		}
		i++;
	}
	for(inst_iterator I=inst_begin(callee), E=inst_end(callee); I != E; ++I){
		Instruction *inst=&*I;
		if(CallInst *call_inst=dyn_cast<CallInst>(inst)){
			Function *sub_callee=call_inst->getCalledFunction();
			unsigned Fnid=sub_callee->getIntrinsicID();
			if(Fnid==0){
				if(sub_callee!=callee){
					keep_array_consistent(sub_callee,call_inst);
				}
			}
		}
	}
}

        if(set_subFn==false){
        	errs()<<"Pipelining of subFun is not set correctly.\n";
        }