Repository: UVA-LavaLab/PIMeval-PIMbench
Pull request #277: Integration of AES S-box API and Refinement of GPU Power and Energy Measurement Methodology
Author: login: mhgholamrezaei
Created: 2025-05-02T10:33:44Z | Merged: 2025-05-07T15:13:28Z

Changes:
- Introduced a dedicated API for the AES S-box operation in the PIMeval header.
- Added a new PIM command by extending the PimCmdFunc1 class to support S-box functionality.
- Implemented performance and energy estimation for the AES S-box on bit-serial architectures in pimPerfEnergyBitSerial.cpp.
- Implemented performance and energy estimation for the AES S-box on bank-level architectures in pimPerfEnergyBankLevel.cpp.
- Updated the AES benchmark to utilize the newly introduced S-box API.
- Refined the methodology for measuring GPU time, power, and energy to improve accuracy and consistency.




Diff:
diff --git a/PIMbench/aes/PIM/aes.cpp b/PIMbench/aes/PIM/aes.cpp
index 9ce6c307..d6140726 100644
--- a/PIMbench/aes/PIM/aes.cpp
+++ b/PIMbench/aes/PIM/aes.cpp
@@ -5,6 +5,7 @@
 
 #include "libpimeval.h"
 #include "PIMAuxilary.h"
+#include "util.h"
 #include <iostream> 
 #include <vector>
 #include <cinttypes>
@@ -22,9 +23,6 @@
 
 #define AES_BLOCK_SIZE 16
 
-#define SUBBYTES_FUNCTIONAL
-#define RJXTIME_FUCNTIONAL
-
 // Function-like macros to avoid repetitive code.
 #define F(x)   (((x) << 1) ^ ((((x) >> 7) & 1) * 0x1B))
 #define FD(x)  (((x) >> 1) ^ (((x) & 1) ? 0x8D : 0))
@@ -113,16 +111,15 @@ typedef struct Params
     const char *inputFile;
     const char *cipherFile;
     const char *outputFile;
+    const char *configFile;
     bool shouldVerify;
 } Params;
  
 // Util functions 
-void inline interrupt(int line); 
 void bufferPadding(uint8_t* inBuf, uint8_t* outBuf, unsigned inNumBytes, unsigned outNumBytes);
-int compare_files(const char *file1, const char *file2);
+int compareFiles(const char *file1, const char *file2);
 void usage();
 struct Params getInputParams(int argc, char **argv); 
-
   
 // CPU Function declarations.
 uint8_t rjXtime(uint8_t x);
@@ -142,7 +139,7 @@ void aes256DecryptEcb(uint8_t *buf, unsigned long offset);
 void encryptdemo(uint8_t key[32], uint8_t *buf, unsigned long numbytes);
 void decryptdemo(uint8_t key[32], uint8_t *buf, unsigned long numbytes);
 
-#define FUNCTION_UNDER_TEST testDemo  
+#define FUNCTION_UNDER_TEST testAesAddRoundKeyCpy  
 
 // Test functions 
 int testRjXtime(void);  
@@ -173,18 +170,14 @@ void aesMixColumns(std::vector<PIMAuxilary*>* inputObjBuf);
 void aesMixColumnsInv(std::vector<PIMAuxilary*>* inputObjBuf);
 void aes256EncryptEcb(std::vector<PIMAuxilary*>* inputObjBuf, unsigned long offset);
 void aes256DecryptEcb(std::vector<PIMAuxilary*>* inputObjBuf, unsigned long offset);
-void encryptdemo(uint8_t key[32], std::vector<PIMAuxilary*>* inputObjBuf, unsigned long numCalls);
-void decryptdemo(uint8_t key[32], std::vector<PIMAuxilary*>* inputObjBuf, unsigned long numCalls);
+void encryptdemo(uint8_t key[32], std::vector<PIMAuxilary*>* inputObjBuf);
+void decryptdemo(uint8_t key[32], std::vector<PIMAuxilary*>* inputObjBuf);
    
 int main(int argc, char **argv){
     srand(time(NULL));
-    // int returnStatus = testEncryptdemo();
-    // int returnStatus = testDecryptdemo();
-    // int returnStatus = FUNCTION_UNDER_TEST();
-    // std::cout << "INFO: Host elapsed time : " << std::fixed << std::setprecision(6) << host_elapsedTime_global.count() << " ms." << std::endl;
-   
+    //int returnStatus = FUNCTION_UNDER_TEST();
     int returnStatus = testDemo(argc, argv);
-    /* TODO: uncomment it. used to avoid core dump in slurm task. */
+    std::cout << "INFO: Host elapsed time : " << std::fixed << std::setprecision(6) << host_elapsedTime_global.count() << " ms." << std::endl;
     assert(returnStatus == 0);
     return 0;
 }
@@ -456,26 +449,12 @@ void decryptdemo(uint8_t key[32], uint8_t *buf, unsigned long numbytes){
 }
 
 int testRjXtime(void){
-
     std::cout << "INFO: PIM test: AES.rjXtime" << std::endl;
+    bool status;
 
-    // Configuration parameters
-    unsigned numRows = 8192;
-    unsigned numRanks = 1; 
-    unsigned numBanks = 1; 
-    unsigned numSubarrayPerBank = 1024;
-    unsigned numCores = numRanks * numBanks * numSubarrayPerBank / 2;
-    unsigned numCols = 1024;
-    unsigned totalElementCount = numCores * numCols;
-
-
-    // Initialize PIM device
-    PimStatus status = pimCreateDevice(PIM_FUNCTIONAL, numRanks, numBanks, numSubarrayPerBank, numRows, numCols);
+    if (!createDevice(NULL)) return 1;
+    unsigned totalElementCount = 1024; 
 
-    assert(status == PIM_OK);
-
-
-    
     PIMAuxilary* xObj = new PIMAuxilary(PIM_ALLOC_AUTO, totalElementCount, PIM_UINT8);
     PIMAuxilary* zObj = new PIMAuxilary(xObj);
 
@@ -512,27 +491,17 @@ int testRjXtime(void){
     return 0;
 }
 int testAesSubBytes() {
-    std::cout << "INFO: PIM test: AES.aesSubBytesInv" << std::endl;
+    std::cout << "INFO: PIM test: AES.aesSubBytes" << std::endl;
+    PimStatus status; 
 
-    // Configuration parameters
-    unsigned numRanks = 1;
-    unsigned numBanks = 1; 
-    unsigned numSubarrayPerBank = 2;
-    unsigned numCores = numRanks * numBanks * numSubarrayPerBank / 2;
-    unsigned numRows = 65536;
-
-    unsigned numCols = 1024; 
-    unsigned totalCols = numCols * numCores;
-    unsigned long numElements = totalCols;
-    unsigned long numBytes = numElements * AES_BLOCK_SIZE;
-   
-    // Initialize PIM device
-    PimStatus status = pimCreateDevice(PIM_FUNCTIONAL, numRanks, numBanks, numSubarrayPerBank, numRows, numCols);
+    if (!createDevice(NULL)) return 1;
+    unsigned totalElementCount = 1024; 
+    unsigned long numBytes = totalElementCount * AES_BLOCK_SIZE;
 
     // Allocate memory for input buffers
     std::vector<PIMAuxilary*> *inputObjBuf = new std::vector<PIMAuxilary*>(AES_BLOCK_SIZE);
 
-    (*inputObjBuf)[0] = new PIMAuxilary(PIM_ALLOC_AUTO, numElements, PIM_UINT8);
+    (*inputObjBuf)[0] = new PIMAuxilary(PIM_ALLOC_AUTO, totalElementCount, PIM_UINT8);
     for (unsigned j = 1; j < AES_BLOCK_SIZE; ++j) {
         (*inputObjBuf)[j] = new PIMAuxilary((*inputObjBuf)[0]);
     }
@@ -585,26 +554,16 @@ int testAesSubBytes() {
 
 int testAesSubBytesInv() {
     std::cout << "INFO: PIM test: AES.aesSubBytesInv" << std::endl;
+    PimStatus status; 
 
-    // Configuration parameters
-    unsigned numRanks = 1;
-    unsigned numBanks = 1; 
-    unsigned numSubarrayPerBank = 2;
-    unsigned numCores = numRanks * numBanks * numSubarrayPerBank / 2;
-    unsigned numRows = 65536;
-
-    unsigned numCols = 1024; 
-    unsigned totalCols = numCols * numCores;
-    unsigned long numElements = totalCols;
-    unsigned long numBytes = numElements * AES_BLOCK_SIZE;
-   
-    // Initialize PIM device
-    PimStatus status = pimCreateDevice(PIM_FUNCTIONAL, numRanks, numBanks, numSubarrayPerBank, numRows, numCols);
+    if (!createDevice(NULL)) return 1;
+    unsigned totalElementCount = 1024; 
+    unsigned long numBytes = totalElementCount * AES_BLOCK_SIZE;
 
     // Allocate memory for input buffers
     std::vector<PIMAuxilary*> *inputObjBuf = new std::vector<PIMAuxilary*>(AES_BLOCK_SIZE);
 
-    (*inputObjBuf)[0] = new PIMAuxilary(PIM_ALLOC_AUTO, numElements, PIM_UINT8);
+    (*inputObjBuf)[0] = new PIMAuxilary(PIM_ALLOC_AUTO, totalElementCount, PIM_UINT8);
     for (unsigned j = 1; j < AES_BLOCK_SIZE; ++j) {
         (*inputObjBuf)[j] = new PIMAuxilary((*inputObjBuf)[0]);
     }
@@ -664,25 +623,15 @@ int testAesSubBytesInv() {
 
 int testAesAddRoundKey() {
     std::cout << "INFO: PIM test: AES.testAesAddRoundKey" << std::endl;
+    PimStatus status; 
 
-    // Configuration parameters
-    unsigned numRanks = 1;
-    unsigned numBanks = 1; 
-    unsigned numSubarrayPerBank = 2;
-    unsigned numCores = numRanks * numBanks * numSubarrayPerBank / 2;
-    unsigned numRows = 65536;
-
-    unsigned numCols = 1024; 
-    unsigned totalCols = numCols * numCores;
-    unsigned long numElements = totalCols;
-   
-    // Initialize PIM device
-    PimStatus status = pimCreateDevice(PIM_FUNCTIONAL, numRanks, numBanks, numSubarrayPerBank, numRows, numCols);
+    if (!createDevice(NULL)) return 1;
+    unsigned totalElementCount = 1024; 
 
     // Allocate memory for input buffers
     std::vector<PIMAuxilary*> *inputObjBuf = new std::vector<PIMAuxilary*>(AES_BLOCK_SIZE);
 
-    (*inputObjBuf)[0] = new PIMAuxilary(PIM_ALLOC_AUTO, numElements, PIM_UINT8);
+    (*inputObjBuf)[0] = new PIMAuxilary(PIM_ALLOC_AUTO, totalElementCount, PIM_UINT8);
     for (unsigned j = 1; j < AES_BLOCK_SIZE; ++j) {
         (*inputObjBuf)[j] = new PIMAuxilary((*inputObjBuf)[0]);
     }
@@ -707,7 +656,7 @@ int testAesAddRoundKey() {
 
     // Copy data to input and key object buffers
     for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
-        for (unsigned i = 0; i < numElements; ++i) {
+        for (unsigned i = 0; i < totalElementCount; ++i) {
             (*inputObjBuf)[j]->array[i] = buf[j];
             (*keyObjBuf)[j]->array[i] = key[j];
             (*outObjBuf)[j]->array[i] = 0;
@@ -751,25 +700,15 @@ int testAesAddRoundKey() {
 
 int testAesAddRoundKeyCpy(void) {
     std::cout << "INFO: PIM test: AES.testAesAddRoundKeyCpy" << std::endl;
+    PimStatus status; 
 
-    // Configuration parameters
-    unsigned numRanks = 1;
-    unsigned numBanks = 1; 
-    unsigned numSubarrayPerBank = 2;
-    unsigned numCores = numRanks * numBanks * numSubarrayPerBank / 2;
-    unsigned numRows = 65536;
-
-    unsigned numCols = 1024; 
-    unsigned totalCols = numCols * numCores;
-    unsigned long numElements = totalCols;
-   
-    // Initialize PIM device
-    PimStatus status = pimCreateDevice(PIM_FUNCTIONAL, numRanks, numBanks, numSubarrayPerBank, numRows, numCols);
+    if (!createDevice(NULL)) return 1;
+    unsigned totalElementCount = 1024; 
 
     // Allocate memory for input buffers
     std::vector<PIMAuxilary*> *inputObjBuf = new std::vector<PIMAuxilary*>(AES_BLOCK_SIZE);
 
-    (*inputObjBuf)[0] = new PIMAuxilary(PIM_ALLOC_AUTO, numElements, PIM_UINT8);
+    (*inputObjBuf)[0] = new PIMAuxilary(PIM_ALLOC_AUTO, totalElementCount, PIM_UINT8);
     for (unsigned j = 1; j < AES_BLOCK_SIZE; ++j) {
         (*inputObjBuf)[j] = new PIMAuxilary((*inputObjBuf)[0]);
     }
@@ -796,7 +735,7 @@ int testAesAddRoundKeyCpy(void) {
     }
 
     for (unsigned j = 0; j < 16; ++j) {
-        for (unsigned i = 0; i < numElements; ++i) {
+        for (unsigned i = 0; i < totalElementCount; ++i) {
             (*inputObjBuf)[j]->array[i] = buf[j];
         }    
     }
@@ -810,7 +749,7 @@ int testAesAddRoundKeyCpy(void) {
     }
 
     for (unsigned j = 0; j < 16; ++j) {
-        for (unsigned i = 0; i < numElements; ++i) {
+        for (unsigned i = 0; i < totalElementCount; ++i) {
             (*keyObjBuf)[j]->array[i] = key[j];
         }    
     }
@@ -868,27 +807,16 @@ int testAesAddRoundKeyCpy(void) {
 
 int testAesShiftRows(void) {
     std::cout << "INFO: PIM test: AES.aesShiftRows" << std::endl;
+    PimStatus status; 
 
-    // Configuration parameters
-    unsigned numRanks = 1;
-    unsigned numBanks = 1; 
-    unsigned numSubarrayPerBank = 2;
-    unsigned numCores = numRanks * numBanks * numSubarrayPerBank / 2;
-    unsigned numRows = 65536;
-
-    unsigned numCols = 1024; 
-    unsigned totalCols = numCols * numCores;
-    unsigned long numElements = totalCols;
-    unsigned long numBytes = numElements * AES_BLOCK_SIZE;
-   
-    // Initialize PIM device
-    PimStatus status = pimCreateDevice(PIM_FUNCTIONAL, numRanks, numBanks, numSubarrayPerBank, numRows, numCols);
-
-
+    if (!createDevice(NULL)) return 1;
+    unsigned totalElementCount = 1024; 
+    unsigned long numBytes = totalElementCount * AES_BLOCK_SIZE;
+    
     // Allocate memory for input buffers
     std::vector<PIMAuxilary*> *inputObjBuf = new std::vector<PIMAuxilary*>(AES_BLOCK_SIZE);
 
-    (*inputObjBuf)[0] = new PIMAuxilary(PIM_ALLOC_AUTO, numElements, PIM_UINT8);
+    (*inputObjBuf)[0] = new PIMAuxilary(PIM_ALLOC_AUTO, totalElementCount, PIM_UINT8);
     for (unsigned j = 1; j < AES_BLOCK_SIZE; ++j) {
         (*inputObjBuf)[j] = new PIMAuxilary((*inputObjBuf)[0]);
 
@@ -947,24 +875,16 @@ int testAesShiftRows(void) {
 
 int testAesShiftRowsInv(void) {
     std::cout << "INFO: PIM test: AES.aesShiftRowsInv" << std::endl;
+    PimStatus status; 
 
-    // Configuration parameters
-    unsigned numBanks = 1; 
-    unsigned numSubarrayPerBank = 1024;
-    unsigned numCores = numBanks * numSubarrayPerBank;
-    unsigned numRows = 65536;
-
-    unsigned numCols = 1024;
-    unsigned long numBytes = numCols * AES_BLOCK_SIZE;
-    unsigned totalElementCount = numCores * numCols / 2;
-
-    // Initialize PIM device
-    PimStatus status = pimCreateDevice(PIM_FUNCTIONAL, 1, numBanks, numSubarrayPerBank, numRows, numCols);
+    if (!createDevice(NULL)) return 1;
+    unsigned totalElementCount = 1024; 
+    unsigned long numBytes = totalElementCount * AES_BLOCK_SIZE;
 
     // Allocate memory for input buffers
     std::vector<PIMAuxilary*> *inputObjBuf = new std::vector<PIMAuxilary*>(AES_BLOCK_SIZE);
 
-    (*inputObjBuf)[0] = new PIMAuxilary(PIM_ALLOC_V1, totalElementCount, PIM_UINT8);
+    (*inputObjBuf)[0] = new PIMAuxilary(PIM_ALLOC_AUTO, totalElementCount, PIM_UINT8);
     for (unsigned j = 1; j < AES_BLOCK_SIZE; ++j) {
         (*inputObjBuf)[j] = new PIMAuxilary((*inputObjBuf)[0]);
 
@@ -1028,40 +948,26 @@ int testAesShiftRowsInv(void) {
 
 int testAesMixColumns(void) {
     std::cout << "INFO: PIM test: AES.aesMixColumns" << std::endl;
+    PimStatus status; 
 
-    // Configuration parameters
-    unsigned numBanks = 1; 
-    unsigned numSubarrayPerBank = 8;
-    unsigned numCores = numBanks * numSubarrayPerBank / 2;
-    unsigned numRows = 65536;
-
-    unsigned numCols = 8; 
-    unsigned totalElementCount = numCores * numCols;
+    if (!createDevice(NULL)) return 1;
+    unsigned totalElementCount = 1024; 
     unsigned long numBytes = totalElementCount * AES_BLOCK_SIZE;
 
-    // Initialize PIM device
-    PimStatus status = pimCreateDevice(PIM_FUNCTIONAL, 1, numBanks, numSubarrayPerBank, numRows, numCols);
-
     // Allocate memory for input buffers
     std::vector<PIMAuxilary*> *inputObjBuf = new std::vector<PIMAuxilary*>(AES_BLOCK_SIZE);
 
-
     (*inputObjBuf)[0]= new PIMAuxilary(PIM_ALLOC_AUTO, totalElementCount, PIM_UINT8);
-
     for (unsigned j = 1; j < (AES_BLOCK_SIZE); ++j) {
         (*inputObjBuf)[j]= new PIMAuxilary((*inputObjBuf)[0]);
     }
 
-    
-
     // Initialize buffer 
     uint8_t bufIn[numBytes]; 
-   
     for (unsigned j = 0; j < numBytes; ++j) {
         bufIn[j] = rand() % 256; 
     }
 
-
     for (unsigned j = 0; j < numBytes; ++j) {
         (*inputObjBuf)[j % (AES_BLOCK_SIZE)]->array[j / (AES_BLOCK_SIZE)] = bufIn[j];
     }
@@ -1105,30 +1011,19 @@ int testAesMixColumns(void) {
 
 int testAesMixColumnsInv(void) {
     std::cout << "INFO: PIM test: AES.testAesMixColumnsInv" << std::endl;
+    PimStatus status; 
 
-    // Configuration parameters
-    unsigned numBanks = 1; 
-    unsigned numSubarrayPerBank = 8;
-    unsigned numCores = numBanks * numSubarrayPerBank / 2;
-    unsigned numRows = 65536;
-
-    unsigned numCols = 8; 
-    unsigned totalElementCount = numCores * numCols;
+    if (!createDevice(NULL)) return 1;
+    unsigned totalElementCount = 1024; 
     unsigned long numBytes = totalElementCount * AES_BLOCK_SIZE;
 
-    // Initialize PIM device
-    PimStatus status = pimCreateDevice(PIM_FUNCTIONAL, 1, numBanks, numSubarrayPerBank, numRows, numCols);
-
     // Allocate memory for input buffers
     std::vector<PIMAuxilary*> *inputObjBuf = new std::vector<PIMAuxilary*>(AES_BLOCK_SIZE);
 
-
     (*inputObjBuf)[0]= new PIMAuxilary(PIM_ALLOC_AUTO, totalElementCount, PIM_UINT8);
-
     for (unsigned j = 1; j < (AES_BLOCK_SIZE); ++j) {
         (*inputObjBuf)[j]= new PIMAuxilary((*inputObjBuf)[0]);
     }
-    
 
     // Initialize buffer 
     uint8_t bufIn[numBytes]; 
@@ -1137,7 +1032,6 @@ int testAesMixColumnsInv(void) {
         bufIn[j] = rand() % 256;
     }
 
-
     for (unsigned j = 0; j < numBytes; ++j) {
         (*inputObjBuf)[j % (AES_BLOCK_SIZE)]->array[j / (AES_BLOCK_SIZE)] = bufIn[j];
     }
@@ -1181,19 +1075,11 @@ int testAesMixColumnsInv(void) {
 
 int testAes256EncryptEcb(void) {
     std::cout << "INFO: PIM test: AES.aes256EncryptEcb" << std::endl;
+    PimStatus status; 
 
-    // Configuration parameters
-    unsigned numBanks = 1; 
-    unsigned numSubarrayPerBank = 2;
-    unsigned numCores = numBanks * numSubarrayPerBank / 2;
-    unsigned numRows = 65536;
-
-    unsigned numCols = 8; 
-    unsigned totalElementCount = numCores * numCols;
+    if (!createDevice(NULL)) return 1;
+    unsigned totalElementCount = 1024; 
     unsigned long numBytes = totalElementCount * AES_BLOCK_SIZE;
-    
-    // Initialize PIM device
-    PimStatus status = pimCreateDevice(PIM_FUNCTIONAL, 1, numBanks, numSubarrayPerBank, numRows, numCols);
 
     // Allocate memory for input buffers
     std::vector<PIMAuxilary*> *inputObjBuf = new std::vector<PIMAuxilary*>(AES_BLOCK_SIZE);
@@ -1266,20 +1152,12 @@ int testAes256EncryptEcb(void) {
 
 int testAes256DecryptEcb(void) {
     std::cout << "INFO: PIM test: AES.aes256DecryptEcb" << std::endl;
+    PimStatus status; 
 
-    // Configuration parameters
-    unsigned numBanks = 1; 
-    unsigned numSubarrayPerBank = 8;
-    unsigned numCores = numBanks * numSubarrayPerBank / 2;
-    unsigned numRows = 65536;
-
-    unsigned numCols = 8; 
-    unsigned totalElementCount = numCores * numCols;
+    if (!createDevice(NULL)) return 1;
+    unsigned totalElementCount = 1024; 
     unsigned long numBytes = totalElementCount * AES_BLOCK_SIZE;
 
-    // Initialize PIM device
-    PimStatus status = pimCreateDevice(PIM_FUNCTIONAL, 1, numBanks, numSubarrayPerBank, numRows, numCols);
-
     // Allocate memory for input buffers
     std::vector<PIMAuxilary*> *inputObjBuf = new std::vector<PIMAuxilary*>(AES_BLOCK_SIZE);
 
@@ -1289,7 +1167,6 @@ int testAes256DecryptEcb(void) {
     for (unsigned j = 1; j < (AES_BLOCK_SIZE); ++j) {
         (*inputObjBuf)[j]= new PIMAuxilary((*inputObjBuf)[0]);
     }
-    
 
     // Initialize buffer 
     uint8_t bufIn[numBytes]; 
@@ -1303,8 +1180,6 @@ int testAes256DecryptEcb(void) {
         key[j] = rand() % 256; 
     }
 
-
-
     for (unsigned j = 0; j < numBytes; ++j) {
         (*inputObjBuf)[j % (AES_BLOCK_SIZE)]->array[j / (AES_BLOCK_SIZE)] = bufIn[j];
     }
@@ -1351,37 +1226,17 @@ int testAes256DecryptEcb(void) {
 
 int testEncryptdemo(void) {
     std::cout << "INFO: PIM test: AES.encryptdemo" << std::endl;
+    PimStatus status; 
 
-    // unsigned long numBytes = 1310720; 
-    unsigned long numBytes = 1UL * 1024 * 1024; // * 1024; // 1 GB
-
-    // Each rank has 8 chips; Total Bank = 16; Each Bank contains 32 subarrays;
-    unsigned numRanks = 2;
-    unsigned numBankPerRank = 1; // 128; // 8 chips * 16 banks
-    unsigned numSubarrayPerBank = 4; // 32;
-    unsigned numRows = 8192;
-    unsigned numCols = 8192;
-    unsigned numCores = numRanks * numBankPerRank * numSubarrayPerBank / 2; 
-    unsigned totalCols = numCores * numCols;
-
-    unsigned numCalls = 1;
-    unsigned numPaddedBufBytes = numBytes;
-    unsigned numElements = numPaddedBufBytes / AES_BLOCK_SIZE;
-
-    {
-        std::cout << "INFO: numCalls = " << numCalls << std::endl; 
-        std::cout << "INFO: numPaddedBufBytes = " << numPaddedBufBytes << std::endl; 
-        std::cout << std::endl;
+    if (!createDevice(NULL)) return 1;
 
-    }
-    
-    PimStatus status = pimCreateDevice(PIM_FUNCTIONAL, numRanks, numBankPerRank, numSubarrayPerBank, numRows, numCols);
-    assert(status == PIM_OK);
+    unsigned long numBytes = 1UL * 1024 * 1024;
+    unsigned totalElementCount = numBytes / AES_BLOCK_SIZE;
 
-    std::vector<PIMAuxilary*> *inputObjBuf = new std::vector<PIMAuxilary*>(AES_BLOCK_SIZE * numCalls);
-    (*inputObjBuf)[0]= new PIMAuxilary(PIM_ALLOC_AUTO, numElements, PIM_UINT8);
+    std::vector<PIMAuxilary*> *inputObjBuf = new std::vector<PIMAuxilary*>(AES_BLOCK_SIZE);
+    (*inputObjBuf)[0]= new PIMAuxilary(PIM_ALLOC_AUTO, totalElementCount, PIM_UINT8);
    
-    for (unsigned j = 1; j < (AES_BLOCK_SIZE * numCalls); ++j) {
+    for (unsigned j = 1; j < AES_BLOCK_SIZE; ++j) {
         (*inputObjBuf)[j]= new PIMAuxilary((*inputObjBuf)[0]);
     }
     
@@ -1392,55 +1247,45 @@ int testEncryptdemo(void) {
     }
 
     // Allocate the input buffer 
-    uint8_t* bufIn = (uint8_t*) malloc(numPaddedBufBytes * sizeof(uint8_t)); 
+    uint8_t* bufIn = (uint8_t*) malloc(numBytes * sizeof(uint8_t)); 
 
     // Initialize buffer s
-    for (unsigned j = 0; j < numPaddedBufBytes; ++j) {
+    for (unsigned j = 0; j < numBytes; ++j) {
         bufIn[j] = rand() % 256; 
     }
 
     // Initialize inputObjBuf
-    unsigned words_per_call, i_call, i_chunk, i_aes_block, remained; 
-    words_per_call = (totalCols * AES_BLOCK_SIZE);
-    for (unsigned j = 0; j < numPaddedBufBytes; ++j) {
-        i_call = j / words_per_call; 
-        remained = j % words_per_call; 
-        i_aes_block = (remained % AES_BLOCK_SIZE) + (i_call * AES_BLOCK_SIZE);
-        i_chunk = remained / AES_BLOCK_SIZE;
-        (*inputObjBuf)[i_aes_block]->array[i_chunk] = bufIn[j]; 
+    for (unsigned j = 0; j < numBytes; ++j) {
+        (*inputObjBuf)[j % (AES_BLOCK_SIZE)]->array[j / (AES_BLOCK_SIZE)] = bufIn[j];
     }
 
     // Copy inputObjBuf to the device 
-    for (unsigned j = 0; j < (AES_BLOCK_SIZE * numCalls); ++j) {
+    for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
         status = pimCopyHostToDevice((*inputObjBuf)[j]->array.data(), (*inputObjBuf)[j]->pimObjId);
         assert(status == PIM_OK);
     }
 
     memcpy(ctx_key_global, key, 32);
-    encryptdemo(key, bufIn, numPaddedBufBytes);   
+    encryptdemo(key, bufIn, numBytes);   
 
     memcpy(ctx_key_global, key, 32);
-    encryptdemo(key, inputObjBuf, numCalls);
+    encryptdemo(key, inputObjBuf);
 
 
-    for (unsigned j = 0; j < (AES_BLOCK_SIZE * numCalls); ++j) {
+    for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
         status = pimCopyDeviceToHost((*inputObjBuf)[j]->pimObjId, (*inputObjBuf)[j]->array.data());
 
         assert(status == PIM_OK);
     }
-
-
    
     pimShowStats();
-    for (unsigned j = 0; j < numPaddedBufBytes; ++j) { 
-        i_call = j / words_per_call; 
-        remained = j % words_per_call; 
-        i_aes_block = (remained % AES_BLOCK_SIZE) + (i_call * AES_BLOCK_SIZE);
-        i_chunk = remained / AES_BLOCK_SIZE;
-        if (((*inputObjBuf)[i_aes_block]->array[i_chunk] & 0xff) != bufIn[j]) {
-            
-            std::cout << "(int)(*inputObjBuf)[" << i_aes_block << "]->array[" << i_chunk << "]: " << (int)(*inputObjBuf)[i_aes_block]->array[i_chunk] << std::endl;
-            std::cout << "buf[" << j << "]: " << (int)bufIn[j] << std::endl;
+    for (unsigned j = 0; j < numBytes; ++j) { 
+        if ((*inputObjBuf)[j % AES_BLOCK_SIZE]->array[j / AES_BLOCK_SIZE ] != bufIn[j]) {
+           
+            for (unsigned i = 0; i < numBytes; ++i) {  
+                std::cout << "(int)(*inputObjBuf)[" << i % AES_BLOCK_SIZE << "]->array[ " << i / AES_BLOCK_SIZE << " ]: " << (int)(*inputObjBuf)[i % AES_BLOCK_SIZE]->array[i / AES_BLOCK_SIZE] << std::endl;
+                std::cout << "buf[" << i << "]: " << (int)bufIn[i] << std::endl;
+            }
             std::cout << "Abort" << std::endl;
             return 1; 
         }
@@ -1449,43 +1294,23 @@ int testEncryptdemo(void) {
     return 0;
 }
 
+
 int testDecryptdemo(void) {
     std::cout << "INFO: PIM test: AES.decryptdemo" << std::endl;
+    PimStatus status; 
 
-    // unsigned long numBytes = 1310720; 
-    unsigned long numBytes = 1UL * 1024 * 1024 * 1024; // 1 GB
-
-    // Each rank has 8 chips; Total Bank = 16; Each Bank contains 32 subarrays;
-    unsigned numRanks = 2;
-    unsigned numBankPerRank = 128; // 8 chips * 16 banks
-    unsigned numSubarrayPerBank = 32;
-    unsigned numRows = 8192;
-    unsigned numCols = 8192;
-    unsigned numCores = numRanks * numBankPerRank * numSubarrayPerBank / 2; 
-    unsigned totalCols = numCores * numCols;
-
-    unsigned numCalls = 1;
-    unsigned numPaddedBufBytes = numBytes;
-    unsigned numElements = numPaddedBufBytes / AES_BLOCK_SIZE;
-
+    if (!createDevice(NULL)) return 1;
 
-    {
-        std::cout << "INFO: numCalls = " << numCalls << std::endl; 
-        std::cout << "INFO: numPaddedBufBytes = " << numPaddedBufBytes << std::endl; 
-        std::cout << std::endl;
+    unsigned long numBytes = 1UL * 1024 * 1024;
+    unsigned totalElementCount = numBytes / AES_BLOCK_SIZE;
 
-    }
-    
-    PimStatus status = pimCreateDevice(PIM_FUNCTIONAL, numRanks, numBankPerRank, numSubarrayPerBank, numRows, numCols);
-    assert(status == PIM_OK);
-
-
-    std::vector<PIMAuxilary*> *inputObjBuf = new std::vector<PIMAuxilary*>(AES_BLOCK_SIZE * numCalls);
-    (*inputObjBuf)[0]= new PIMAuxilary(PIM_ALLOC_AUTO, numElements, PIM_UINT8);
+    std::vector<PIMAuxilary*> *inputObjBuf = new std::vector<PIMAuxilary*>(AES_BLOCK_SIZE);
+    (*inputObjBuf)[0]= new PIMAuxilary(PIM_ALLOC_AUTO, totalElementCount, PIM_UINT8);
    
-    for (unsigned j = 1; j < (AES_BLOCK_SIZE * numCalls); ++j) {
+    for (unsigned j = 1; j < AES_BLOCK_SIZE; ++j) {
         (*inputObjBuf)[j]= new PIMAuxilary((*inputObjBuf)[0]);
     }
+    
     uint8_t key[32];
     for (unsigned j = 0; j < 32; ++j)
     {
@@ -1493,52 +1318,45 @@ int testDecryptdemo(void) {
     }
 
     // Allocate the input buffer 
-    uint8_t* bufIn = (uint8_t*) malloc(numPaddedBufBytes * sizeof(uint8_t)); 
+    uint8_t* bufIn = (uint8_t*) malloc(numBytes * sizeof(uint8_t)); 
 
-    // Initialize buffer 
-    for (unsigned j = 0; j < numPaddedBufBytes; ++j) {
+    // Initialize buffer s
+    for (unsigned j = 0; j < numBytes; ++j) {
         bufIn[j] = rand() % 256; 
     }
 
     // Initialize inputObjBuf
-    unsigned words_per_call, i_call, i_chunk, i_aes_block, remained; 
-    words_per_call = (totalCols * AES_BLOCK_SIZE);
-    for (unsigned j = 0; j < numPaddedBufBytes; ++j) {
-        i_call = j / words_per_call; 
-        remained = j % words_per_call; 
-        i_aes_block = (remained % AES_BLOCK_SIZE) + (i_call * AES_BLOCK_SIZE);
-        i_chunk = remained / AES_BLOCK_SIZE;
-        (*inputObjBuf)[i_aes_block]->array[i_chunk] = bufIn[j]; 
+    for (unsigned j = 0; j < numBytes; ++j) {
+        (*inputObjBuf)[j % (AES_BLOCK_SIZE)]->array[j / (AES_BLOCK_SIZE)] = bufIn[j];
     }
 
     // Copy inputObjBuf to the device 
-    for (unsigned j = 0; j < (AES_BLOCK_SIZE * numCalls); ++j) {
+    for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
         status = pimCopyHostToDevice((*inputObjBuf)[j]->array.data(), (*inputObjBuf)[j]->pimObjId);
         assert(status == PIM_OK);
     }
-    
+
     memcpy(ctx_key_global, key, 32);
-    decryptdemo(key, bufIn, numPaddedBufBytes);   
+    decryptdemo(key, bufIn, numBytes);   
 
     memcpy(ctx_key_global, key, 32);
-    decryptdemo(key, inputObjBuf, numCalls);
+    decryptdemo(key, inputObjBuf);
 
-    for (unsigned j = 0; j < (AES_BLOCK_SIZE * numCalls); ++j) {
+
+    for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
         status = pimCopyDeviceToHost((*inputObjBuf)[j]->pimObjId, (*inputObjBuf)[j]->array.data());
 
         assert(status == PIM_OK);
     }
    
     pimShowStats();
-    for (unsigned j = 0; j < numPaddedBufBytes; ++j) { 
-        i_call = j / words_per_call; 
-        remained = j % words_per_call; 
-        i_aes_block = (remained % AES_BLOCK_SIZE) + (i_call * AES_BLOCK_SIZE);
-        i_chunk = remained / AES_BLOCK_SIZE;
-        if (((*inputObjBuf)[i_aes_block]->array[i_chunk] & 0xff) != bufIn[j]) {
-            
-            std::cout << "(int)(*inputObjBuf)[" << i_aes_block << "]->array[" << i_chunk << "]: " << ((int)(*inputObjBuf)[i_aes_block]->array[i_chunk] & 0xff) << std::endl;
-            std::cout << "buf[" << j << "]: " << (int)bufIn[j] << std::endl;
+    for (unsigned j = 0; j < numBytes; ++j) { 
+        if ((*inputObjBuf)[j % AES_BLOCK_SIZE]->array[j / AES_BLOCK_SIZE ] != bufIn[j]) {
+           
+            for (unsigned i = 0; i < numBytes; ++i) {  
+                std::cout << "(int)(*inputObjBuf)[" << i % AES_BLOCK_SIZE << "]->array[ " << i / AES_BLOCK_SIZE << " ]: " << (int)(*inputObjBuf)[i % AES_BLOCK_SIZE]->array[i / AES_BLOCK_SIZE] << std::endl;
+                std::cout << "buf[" << i << "]: " << (int)bufIn[i] << std::endl;
+            }
             std::cout << "Abort" << std::endl;
             return 1; 
         }
@@ -1549,10 +1367,10 @@ int testDecryptdemo(void) {
 
 int testDemo(int argc, char **argv) {
     struct Params params = getInputParams(argc, argv);
-    
-    FILE *file;
+    bool status; 
+    FILE *file = NULL;
     uint8_t *buf;
-    unsigned long numbytes;
+    unsigned long numBytes;
     int padding;
     uint8_t key[32]; // Encryption/Decryption key.
 
@@ -1595,7 +1413,7 @@ int testDemo(int argc, char **argv) {
     bool generateRandomInput = false;
     if (params.inputFile == NULL) {
         generateRandomInput = true;
-        numbytes = params.inputSize;
+        numBytes = params.inputSize;
         printf("INFO: Input file is not specifed. Random input will be used.\n");
     }
     if (!generateRandomInput) {
@@ -1606,21 +1424,23 @@ int testDemo(int argc, char **argv) {
     }
     if (!generateRandomInput) {
         fseek(file, 0L, SEEK_END);
-        numbytes = ftell(file);
+        numBytes = ftell(file);
         fseek(file, 0L, SEEK_SET);
     }
 
     
     // Allocate memory for the file content.
-    buf = (uint8_t*)malloc(numbytes * sizeof(uint8_t));
+    buf = (uint8_t*)malloc(numBytes * sizeof(uint8_t));
     if (buf == NULL) {
         printf("ERROR: Memory allocation error\n");
-        fclose(file);
+        if (file != NULL) {
+          fclose(file);
+        }
         return EXIT_FAILURE;
     }
 
     // Read the file into the buffer.
-    if (!generateRandomInput && fread(buf, 1, numbytes, file) != numbytes) {
+    if (!generateRandomInput && fread(buf, 1, numBytes, file) != numBytes) {
         printf("ERROR: Unable to read all bytes from file %s\n", params.inputFile);
         fclose(file);
         free(buf);
@@ -1635,58 +1455,40 @@ int testDemo(int argc, char **argv) {
     }
   
     // generate padding
-    padding = numbytes % AES_BLOCK_SIZE;
-    numbytes += padding;
-    printf("INFO: Padding file with %d bytes for a new size of %lu\n", padding, numbytes);
-
-    // Each rank has 8 chips; Total Bank = 16; Each Bank contains 32 subarrays;
-    unsigned numRanks = 2;
-    unsigned numBankPerRank = 128; // 8 chips * 16 banks
-    unsigned numSubarrayPerBank = 32;
-    unsigned numRows = 8192;
-    unsigned numCols = 8192;
-    unsigned numCores = numRanks * numBankPerRank * numSubarrayPerBank / 2; 
-    unsigned totalCols = numCores * numCols;
-
-    unsigned numCalls = 1;
-    unsigned numPaddedBufBytes = numbytes;
-    unsigned numElements = numPaddedBufBytes / AES_BLOCK_SIZE;
-
-    PimStatus status = pimCreateDevice(PIM_FUNCTIONAL, numRanks, numBankPerRank, numSubarrayPerBank, numRows, numCols);
-    assert(status == PIM_OK);
+    padding = AES_BLOCK_SIZE - (numBytes % AES_BLOCK_SIZE);
+    unsigned long numPaddedBufBytes = numBytes + padding; 
+    printf("INFO: Padding file with %d bytes for a new size of %lu\n", padding, numPaddedBufBytes);
+        
+    if (!createDevice(params.configFile)) return 1;
 
-    std::vector<PIMAuxilary*> *inputObjBuf = new std::vector<PIMAuxilary*>(AES_BLOCK_SIZE * numCalls);
+    unsigned numElements = numPaddedBufBytes / AES_BLOCK_SIZE;
+    std::vector<PIMAuxilary*> *inputObjBuf = new std::vector<PIMAuxilary*>(AES_BLOCK_SIZE);
     (*inputObjBuf)[0]= new PIMAuxilary(PIM_ALLOC_AUTO, numElements, PIM_UINT8);
-    for (unsigned j = 1; j < (AES_BLOCK_SIZE * numCalls); ++j) {
+    for (unsigned j = 1; j < AES_BLOCK_SIZE; ++j) {
         (*inputObjBuf)[j]= new PIMAuxilary((*inputObjBuf)[0]);
     }
     
     // Allocate the input buffer 
-    uint8_t* bufIn = (uint8_t*) malloc(numbytes * sizeof(uint8_t)); 
+    uint8_t* bufIn = (uint8_t*) malloc(numBytes * sizeof(uint8_t)); 
     uint8_t* bufInPadded = (uint8_t*) malloc(numPaddedBufBytes * sizeof(uint8_t)); 
 
     // Initialize buffer 
-    for (unsigned j = 0; j < numbytes; ++j) {
+    for (unsigned j = 0; j < numBytes; ++j) {
         bufIn[j] = buf[j];
     }
 
     // Pad the buffer with zero and update the pointer.
-    bufferPadding(bufIn, bufInPadded, numbytes, numPaddedBufBytes);
+    bufferPadding(bufIn, bufInPadded, numBytes, numPaddedBufBytes);
     free(bufIn);
     bufIn = bufInPadded;
 
     // Initialize inputObjBuf
-    unsigned words_per_call, i_call, i_chunk, i_aes_block, remained; 
-    words_per_call = (totalCols * AES_BLOCK_SIZE);
     for (unsigned j = 0; j < numPaddedBufBytes; ++j) {
-        i_call = j / words_per_call; 
-        remained = j % words_per_call; 
-        i_aes_block = (remained % AES_BLOCK_SIZE) + (i_call * AES_BLOCK_SIZE);
-        i_chunk = remained / AES_BLOCK_SIZE;
-        (*inputObjBuf)[i_aes_block]->array[i_chunk] = bufIn[j]; 
+        (*inputObjBuf)[j % (AES_BLOCK_SIZE)]->array[j / (AES_BLOCK_SIZE)] = bufIn[j];
     }
+    
     // Copy inputObjBuf to the device 
-    for (unsigned j = 0; j < (AES_BLOCK_SIZE * numCalls); ++j) {
+    for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
         status = pimCopyHostToDevice((*inputObjBuf)[j]->array.data(), (*inputObjBuf)[j]->pimObjId);
         assert(status == PIM_OK);
     }
@@ -1695,76 +1497,51 @@ int testDemo(int argc, char **argv) {
     encryptdemo(key, bufIn, numPaddedBufBytes);    
     
     memcpy(ctx_key_global, key, 32);
-    encryptdemo(key, inputObjBuf, numCalls);
+    encryptdemo(key, inputObjBuf);
         
-    for (unsigned j = 0; j < (AES_BLOCK_SIZE * numCalls); ++j) {
+    for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
             status = pimCopyDeviceToHost((*inputObjBuf)[j]->pimObjId, (*inputObjBuf)[j]->array.data());
             assert(status == PIM_OK);
     }
  
     pimShowStats();
+    pimResetStats();
 
-    for (unsigned j = 0; j < numPaddedBufBytes; ++j) { 
-        i_call = j / words_per_call; 
-        remained = j % words_per_call; 
-        i_aes_block = (remained % AES_BLOCK_SIZE) + (i_call * AES_BLOCK_SIZE);
-        i_chunk = remained / AES_BLOCK_SIZE;
-        if ((*inputObjBuf)[i_aes_block]->array[i_chunk] != bufIn[j]) {
-            
-            std::cout << "(int)(*inputObjBuf)[" << i_aes_block << "]->array[" << i_chunk << "]: " << (int)(*inputObjBuf)[i_aes_block]->array[i_chunk] << std::endl;
-            std::cout << "buf[" << j << "]: " << (int)bufIn[j] << std::endl;
-            std::cout << "ERROR: Abort" << std::endl;
-            return 1; 
-        }
-    }
- 
     // write the ciphertext to file
     file = fopen(params.cipherFile, "w");
-    fwrite(buf, 1, numbytes, file);
+    fwrite(buf, 1, numBytes, file);
     fclose(file);
-   
-    // return EXIT_SUCCESS;
-
 
     memcpy(ctx_key_global, key, 32);
     decryptdemo(key, bufIn, numPaddedBufBytes);
     
     memcpy(ctx_key_global, key, 32);
-    decryptdemo(key, inputObjBuf, numCalls);
+    decryptdemo(key, inputObjBuf);
 
     pimShowStats();
           
-    for (unsigned j = 0; j < (AES_BLOCK_SIZE * numCalls); ++j) {
+    for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
             status = pimCopyDeviceToHost((*inputObjBuf)[j]->pimObjId, (*inputObjBuf)[j]->array.data());
             assert(status == PIM_OK);
     }
 
-    for (unsigned j = 0; j < numPaddedBufBytes; ++j) { 
-        i_call = j / words_per_call; 
-        remained = j % words_per_call; 
-        i_aes_block = (remained % AES_BLOCK_SIZE) + (i_call * AES_BLOCK_SIZE);
-        i_chunk = remained / AES_BLOCK_SIZE;
-        if ((*inputObjBuf)[i_aes_block]->array[i_chunk] != bufIn[j]) {
-            
-            std::cout << "(int)(*inputObjBuf)[" << i_aes_block << "]->array[" << i_chunk << "]: " << (int)(*inputObjBuf)[i_aes_block]->array[i_chunk] << std::endl;
-            std::cout << "buf[" << j << "]: " << (int)bufIn[j] << std::endl;
-            std::cout << "ERROR: Abort" << std::endl;
-            return 1; 
-        }
-    }
-
     // write to file
     file = fopen(params.outputFile, "w");
-    fwrite(buf, 1, numbytes - padding, file);
+    fwrite(buf, 1, numBytes, file);
     fclose(file);
 
     // Compare input and output files
     if (params.shouldVerify) { 
-        if (compare_files(params.inputFile, params.outputFile) == 0) {
+      if (params.inputFile == NULL) {
+        printf("ERROR: Verification works when an input file is provided.\n");
+      }
+      else {
+        if (compareFiles(params.inputFile, params.outputFile) == 0) {
             printf("INFO: The input file and the output file are the same.\n");
         } else {
             printf("ERROR: The input file and the output file are different.\n");
         }
+      }
     }
 
     free(buf);
@@ -1775,32 +1552,28 @@ void rjXtime(PIMAuxilary* xObj, PIMAuxilary* returnValueObj){
     int status; 
 
     PIMAuxilary* shiftedObj = new PIMAuxilary(xObj);
-    status = pimCopyDeviceToDevice(xObj->pimObjId, shiftedObj->pimObjId);
+    status = pimCopyObjectToObject(xObj->pimObjId, shiftedObj->pimObjId);
     assert(status == PIM_OK);
     // uint8_t shifted = x << 1;
     pimShiftBitsLeft(shiftedObj->pimObjId, shiftedObj->pimObjId, 1);
 
     // uint8_t mask = x;
     PIMAuxilary* maskObj = new PIMAuxilary(xObj);
-    status = pimCopyDeviceToDevice(xObj->pimObjId, maskObj->pimObjId);
+    status = pimCopyObjectToObject(xObj->pimObjId, maskObj->pimObjId);
     assert(status == PIM_OK);
     
     uint8_t const1 = 0x80;
 
     // mask = mask & const1;
-    // status = pimAnd(maskObj->pimObjId, const1Obj->pimObjId, maskObj->pimObjId);
     status = pimAndScalar(maskObj->pimObjId, maskObj->pimObjId, const1);
     assert(status == PIM_OK);
     
     // mask = mask >> 7;  
     pimShiftBitsRight(maskObj->pimObjId, maskObj->pimObjId, 7);
 
-    /* TODO: Replace pimMul with 1-bit to 8-bit AND operation */
-#ifdef RJXTIME_FUCNTIONAL
-    pimMulScalar(maskObj->pimObjId, maskObj->pimObjId, 0x1b);
-#else 
-    pimAndScalar(maskObj->pimObjId, maskObj->pimObjId, 0x1b);
-#endif
+    PimObjId condObj = pimAllocAssociated(maskObj->pimObjId, PIM_BOOL);
+    pimBitSliceExtract(maskObj->pimObjId, condObj, 0);
+    pimCondBroadcast(condObj, 0x1b, maskObj->pimObjId);
 
     pimXor(maskObj->pimObjId, shiftedObj->pimObjId, returnValueObj->pimObjId);
     assert (status == PIM_OK);
@@ -1810,97 +1583,83 @@ void rjXtime(PIMAuxilary* xObj, PIMAuxilary* returnValueObj){
 }
 
 void aesSubBytes(std::vector<PIMAuxilary*>* inputObjBuf) {
+  struct PimDeviceProperties pimDevicePropoerties;
+  pimGetDeviceProperties(&pimDevicePropoerties);
+  if (pimDevicePropoerties.simTarget == PIM_DEVICE_FULCRUM) {
+    int status;
+    // Copy input buffer back to the host 
+    for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
+       status = pimCopyDeviceToHost((*inputObjBuf)[j]->pimObjId, (void*)(*inputObjBuf)[j]->array.data());
+        assert(status == PIM_OK);
+    }
 
-    /* TODO: Implementation based on bit-serial look-up table */
-#ifdef SUBBYTES_FUNCTIONAL
-     int status;
-     // Copy input buffer back to the host 
-     for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
-         status = pimCopyDeviceToHost((*inputObjBuf)[j]->pimObjId, (void*)(*inputObjBuf)[j]->array.data());
-          assert(status == PIM_OK);
-     }
-
-     auto start = std::chrono::high_resolution_clock::now();
-     uint8_t b;
-     for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j)
-     {
-         #pragma omp parallel for 
-         for (unsigned i = 0; i < (*inputObjBuf)[0]->numElements; ++i) {
-             b = (*inputObjBuf)[j]->array[i];
-             (*inputObjBuf)[j]->array[i] = sbox[b];
-         }
-     }
-     auto end = std::chrono::high_resolution_clock::now();
-     host_elapsedTime_global += (end - start);
-
-     // Copy input buffer to the device 
-     for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
-         status = pimCopyHostToDevice((void*)(*inputObjBuf)[j]->array.data(), (*inputObjBuf)[j]->pimObjId);
-         assert(status == PIM_OK);
-     }
-#else     
-    int totalAndOperations = 318 * AES_BLOCK_SIZE / 8; 
-    int orOperations = 415 * AES_BLOCK_SIZE / 8 ;
-    int NotOperations = 8 * AES_BLOCK_SIZE / 8; 
-
-    for (int i = 0; i < totalAndOperations; i++) {
-        pimAnd((*inputObjBuf)[0]->pimObjId, (*inputObjBuf)[0]->pimObjId, (*inputObjBuf)[0]->pimObjId);
-    }
-
-    for (int i = 0; i < orOperations; i++) {
-        pimOr((*inputObjBuf)[0]->pimObjId, (*inputObjBuf)[0]->pimObjId, (*inputObjBuf)[0]->pimObjId);
-    }
-#endif    
-    /* END */
+    auto start = std::chrono::high_resolution_clock::now();
+    uint8_t b;
+    for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j)
+    {
+       #pragma omp parallel for 
+       for (unsigned i = 0; i < (*inputObjBuf)[0]->numElements; ++i) {
+           b = (*inputObjBuf)[j]->array[i];
+           (*inputObjBuf)[j]->array[i] = sbox[b];
+       }
+    }
+    auto end = std::chrono::high_resolution_clock::now();
+    host_elapsedTime_global += (end - start);
 
+    // Copy input buffer to the device 
+    for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
+       status = pimCopyHostToDevice((void*)(*inputObjBuf)[j]->array.data(), (*inputObjBuf)[j]->pimObjId);
+       assert(status == PIM_OK);
+    }
+  }
+  else {
+    int status; 
+    std::vector<uint8_t> lut_vec(std::begin(sbox), std::end(sbox));
+    for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
+      status = pimAesSbox((*inputObjBuf)[j]->pimObjId, (*inputObjBuf)[j]->pimObjId, lut_vec);
+      assert(status == PIM_OK);
+    }
+  }
 }
 
 void aesSubBytesInv(std::vector<PIMAuxilary*>* inputObjBuf) {
-
-    /* TODO: Implementation based on bit-serial look-up table */
-#ifdef SUBBYTES_FUNCTIONAL
+  struct PimDeviceProperties pimDevicePropoerties;
+  pimGetDeviceProperties(&pimDevicePropoerties);
+  if (pimDevicePropoerties.simTarget == PIM_DEVICE_FULCRUM) {
     int status;
-    // Copy input buffer to the host 
-        for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
-        status = pimCopyDeviceToHost((*inputObjBuf)[j]->pimObjId, (void*)(*inputObjBuf)[j]->array.data());
+    // Copy input buffer back to the host 
+    for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
+       status = pimCopyDeviceToHost((*inputObjBuf)[j]->pimObjId, (void*)(*inputObjBuf)[j]->array.data());
         assert(status == PIM_OK);
     }
-    
+
     auto start = std::chrono::high_resolution_clock::now();
     uint8_t b;
-    #pragma omp parallel for 
     for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j)
     {
-        for (unsigned i = 0; i < (*inputObjBuf)[0]->numElements; ++i) {
-            b = (*inputObjBuf)[j]->array[i];
-            (*inputObjBuf)[j]->array[i] = sboxinv[b];
-        }   
+       #pragma omp parallel for 
+       for (unsigned i = 0; i < (*inputObjBuf)[0]->numElements; ++i) {
+           b = (*inputObjBuf)[j]->array[i];
+           (*inputObjBuf)[j]->array[i] = sboxinv[b];
+       }
     }
     auto end = std::chrono::high_resolution_clock::now();
     host_elapsedTime_global += (end - start);
 
-    // Copy input buffer back to the device 
+    // Copy input buffer to the device 
     for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
-        status = pimCopyHostToDevice((void*)(*inputObjBuf)[j]->array.data(), (*inputObjBuf)[j]->pimObjId);
-        assert(status == PIM_OK);       
+       status = pimCopyHostToDevice((void*)(*inputObjBuf)[j]->array.data(), (*inputObjBuf)[j]->pimObjId);
+       assert(status == PIM_OK);
     }
-
-#else
-    int totalAndOperations = 302 * AES_BLOCK_SIZE / 8; 
-    int orOperations = 428 * AES_BLOCK_SIZE / 8 ;
-    int NotOperations = 37 * AES_BLOCK_SIZE / 8; 
-
-    for (int i = 0; i < totalAndOperations; i++) {
-        pimAnd((*inputObjBuf)[0]->pimObjId, (*inputObjBuf)[0]->pimObjId, (*inputObjBuf)[0]->pimObjId);
-    }
-
-    for (int i = 0; i < orOperations; i++) {
-        pimOr((*inputObjBuf)[0]->pimObjId, (*inputObjBuf)[0]->pimObjId, (*inputObjBuf)[0]->pimObjId);
+  }
+  else {
+    int status; 
+    std::vector<uint8_t> lut_vec(std::begin(sboxinv), std::end(sboxinv));
+    for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
+      status = pimAesInverseSbox((*inputObjBuf)[j]->pimObjId, (*inputObjBuf)[j]->pimObjId, lut_vec);
+      assert(status == PIM_OK);
     }
-
-   
-#endif
-    /* END */
+  }
 }
 
 void aesAddRoundKey(std::vector<PIMAuxilary*>* inputObjBuf,std::vector<PIMAuxilary*>* keyObjBuf, std::vector<PIMAuxilary*>* outObjBuf) {
@@ -1911,13 +1670,13 @@ void aesAddRoundKey(std::vector<PIMAuxilary*>* inputObjBuf,std::vector<PIMAuxila
 
 void aesAddRoundKeyCpy(std::vector<PIMAuxilary*>* inputObjBuf,std::vector<PIMAuxilary*>* keyObjBuf, std::vector<PIMAuxilary*>* cpkObjBuf, std::vector<PIMAuxilary*>* outObjBuf) {
     for (int j = 0; j < 16; j++){ 
-        pimCopyDeviceToDevice((*keyObjBuf)[j]->pimObjId, (*cpkObjBuf)[j]->pimObjId);
+        pimCopyObjectToObject((*keyObjBuf)[j]->pimObjId, (*cpkObjBuf)[j]->pimObjId);
     } 
     for (int j = 0; j < 16; j++){ 
         pimXor((*inputObjBuf)[j]->pimObjId, (*cpkObjBuf)[j]->pimObjId, (*outObjBuf)[j]->pimObjId); 
     }   
     for (int j = 0; j < 16; j++){ 
-        pimCopyDeviceToDevice((*keyObjBuf)[16 + j]->pimObjId, (*cpkObjBuf)[16 + j]->pimObjId);
+        pimCopyObjectToObject((*keyObjBuf)[16 + j]->pimObjId, (*cpkObjBuf)[16 + j]->pimObjId);
     } 
 }
 
@@ -1925,22 +1684,22 @@ void aesShiftRows(std::vector<PIMAuxilary*>* inputObjBuf) {
     PIMAuxilary* iObj = new PIMAuxilary((*inputObjBuf)[0]);
     PIMAuxilary* jObj = new PIMAuxilary((*inputObjBuf)[0]);
  
-    pimCopyDeviceToDevice((*inputObjBuf)[1]->pimObjId, iObj->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[5]->pimObjId, (*inputObjBuf)[1]->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[9]->pimObjId, (*inputObjBuf)[5]->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[13]->pimObjId, (*inputObjBuf)[9]->pimObjId);
-    pimCopyDeviceToDevice(iObj->pimObjId, (*inputObjBuf)[13]->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[10]->pimObjId, iObj->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[2]->pimObjId, (*inputObjBuf)[10]->pimObjId);
-    pimCopyDeviceToDevice(iObj->pimObjId, (*inputObjBuf)[2]->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[3]->pimObjId, jObj->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[15]->pimObjId, (*inputObjBuf)[3]->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[11]->pimObjId, (*inputObjBuf)[15]->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[7]->pimObjId, (*inputObjBuf)[11]->pimObjId);
-    pimCopyDeviceToDevice(jObj->pimObjId, (*inputObjBuf)[7]->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[14]->pimObjId, jObj->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[6]->pimObjId, (*inputObjBuf)[14]->pimObjId);
-    pimCopyDeviceToDevice(jObj->pimObjId, (*inputObjBuf)[6]->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[1]->pimObjId, iObj->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[5]->pimObjId, (*inputObjBuf)[1]->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[9]->pimObjId, (*inputObjBuf)[5]->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[13]->pimObjId, (*inputObjBuf)[9]->pimObjId);
+    pimCopyObjectToObject(iObj->pimObjId, (*inputObjBuf)[13]->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[10]->pimObjId, iObj->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[2]->pimObjId, (*inputObjBuf)[10]->pimObjId);
+    pimCopyObjectToObject(iObj->pimObjId, (*inputObjBuf)[2]->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[3]->pimObjId, jObj->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[15]->pimObjId, (*inputObjBuf)[3]->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[11]->pimObjId, (*inputObjBuf)[15]->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[7]->pimObjId, (*inputObjBuf)[11]->pimObjId);
+    pimCopyObjectToObject(jObj->pimObjId, (*inputObjBuf)[7]->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[14]->pimObjId, jObj->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[6]->pimObjId, (*inputObjBuf)[14]->pimObjId);
+    pimCopyObjectToObject(jObj->pimObjId, (*inputObjBuf)[6]->pimObjId);
 
     pimFree(iObj->pimObjId);
     pimFree(jObj->pimObjId);
@@ -1951,22 +1710,22 @@ void aesShiftRowsInv(std::vector<PIMAuxilary*>* inputObjBuf) {
     PIMAuxilary* iObj = new PIMAuxilary((*inputObjBuf)[0]);
     PIMAuxilary* jObj = new PIMAuxilary((*inputObjBuf)[0]);
 
-    pimCopyDeviceToDevice((*inputObjBuf)[1]->pimObjId, iObj->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[13]->pimObjId, (*inputObjBuf)[1]->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[9]->pimObjId, (*inputObjBuf)[13]->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[5]->pimObjId, (*inputObjBuf)[9]->pimObjId);
-    pimCopyDeviceToDevice(iObj->pimObjId, (*inputObjBuf)[5]->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[2]->pimObjId, iObj->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[10]->pimObjId, (*inputObjBuf)[2]->pimObjId);
-    pimCopyDeviceToDevice(iObj->pimObjId, (*inputObjBuf)[10]->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[3]->pimObjId, jObj->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[7]->pimObjId, (*inputObjBuf)[3]->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[11]->pimObjId, (*inputObjBuf)[7]->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[15]->pimObjId, (*inputObjBuf)[11]->pimObjId);
-    pimCopyDeviceToDevice(jObj->pimObjId, (*inputObjBuf)[15]->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[6]->pimObjId, jObj->pimObjId);
-    pimCopyDeviceToDevice((*inputObjBuf)[14]->pimObjId, (*inputObjBuf)[6]->pimObjId);
-    pimCopyDeviceToDevice(jObj->pimObjId, (*inputObjBuf)[14]->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[1]->pimObjId, iObj->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[13]->pimObjId, (*inputObjBuf)[1]->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[9]->pimObjId, (*inputObjBuf)[13]->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[5]->pimObjId, (*inputObjBuf)[9]->pimObjId);
+    pimCopyObjectToObject(iObj->pimObjId, (*inputObjBuf)[5]->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[2]->pimObjId, iObj->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[10]->pimObjId, (*inputObjBuf)[2]->pimObjId);
+    pimCopyObjectToObject(iObj->pimObjId, (*inputObjBuf)[10]->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[3]->pimObjId, jObj->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[7]->pimObjId, (*inputObjBuf)[3]->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[11]->pimObjId, (*inputObjBuf)[7]->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[15]->pimObjId, (*inputObjBuf)[11]->pimObjId);
+    pimCopyObjectToObject(jObj->pimObjId, (*inputObjBuf)[15]->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[6]->pimObjId, jObj->pimObjId);
+    pimCopyObjectToObject((*inputObjBuf)[14]->pimObjId, (*inputObjBuf)[6]->pimObjId);
+    pimCopyObjectToObject(jObj->pimObjId, (*inputObjBuf)[14]->pimObjId);
 
     pimFree(iObj->pimObjId);
     pimFree(jObj->pimObjId);
@@ -1985,16 +1744,16 @@ void aesMixColumns(std::vector<PIMAuxilary*>* inputObjBuf) {
 
     for (int j = 0; j < 16; j += 4){
         //  a = buf[j];
-        pimCopyDeviceToDevice((*inputObjBuf)[j]->pimObjId, aObj->pimObjId);
+        pimCopyObjectToObject((*inputObjBuf)[j]->pimObjId, aObj->pimObjId);
 
         // b = buf[j + 1];
-        pimCopyDeviceToDevice((*inputObjBuf)[j + 1]->pimObjId, bObj->pimObjId);
+        pimCopyObjectToObject((*inputObjBuf)[j + 1]->pimObjId, bObj->pimObjId);
 
         // c = buf[j + 2];
-        pimCopyDeviceToDevice((*inputObjBuf)[j + 2]->pimObjId, cObj->pimObjId);
+        pimCopyObjectToObject((*inputObjBuf)[j + 2]->pimObjId, cObj->pimObjId);
 
         // d = buf[j + 3];
-        pimCopyDeviceToDevice((*inputObjBuf)[j + 3]->pimObjId, dObj->pimObjId);
+        pimCopyObjectToObject((*inputObjBuf)[j + 3]->pimObjId, dObj->pimObjId);
         
         // e = a ^ b;
         pimXor(aObj->pimObjId, bObj->pimObjId, eObj->pimObjId); 
@@ -2018,7 +1777,7 @@ void aesMixColumns(std::vector<PIMAuxilary*>* inputObjBuf) {
         pimXor((*inputObjBuf)[j]->pimObjId, tObj->pimObjId, fObj->pimObjId);
 
         // buf[j] = f
-        pimCopyDeviceToDevice(fObj->pimObjId, (*inputObjBuf)[j]->pimObjId);
+        pimCopyObjectToObject(fObj->pimObjId, (*inputObjBuf)[j]->pimObjId);
 
 
         // t = b ^ c; 
@@ -2035,7 +1794,7 @@ void aesMixColumns(std::vector<PIMAuxilary*>* inputObjBuf) {
         pimXor((*inputObjBuf)[j + 1]->pimObjId, tObj->pimObjId, bObj->pimObjId);   
 
         // buf[j+1] = b
-        pimCopyDeviceToDevice(bObj->pimObjId, (*inputObjBuf)[j + 1]->pimObjId);
+        pimCopyObjectToObject(bObj->pimObjId, (*inputObjBuf)[j + 1]->pimObjId);
 
         // t = c ^ d; 
         pimXor(cObj->pimObjId, dObj->pimObjId, tObj->pimObjId);
@@ -2050,7 +1809,7 @@ void aesMixColumns(std::vector<PIMAuxilary*>* inputObjBuf) {
         pimXor((*inputObjBuf)[j + 2]->pimObjId, tObj->pimObjId, cObj->pimObjId);
 
         // buf[j+2] = c
-        pimCopyDeviceToDevice(cObj->pimObjId, (*inputObjBuf)[j + 2]->pimObjId);
+        pimCopyObjectToObject(cObj->pimObjId, (*inputObjBuf)[j + 2]->pimObjId);
 
         // t = d ^ a; 
         pimXor(dObj->pimObjId, aObj->pimObjId, tObj->pimObjId);
@@ -2065,7 +1824,7 @@ void aesMixColumns(std::vector<PIMAuxilary*>* inputObjBuf) {
         pimXor((*inputObjBuf)[j + 3]->pimObjId, tObj->pimObjId, dObj->pimObjId);
 
         // buf[j+3] = d
-        pimCopyDeviceToDevice(dObj->pimObjId, (*inputObjBuf)[j + 3]->pimObjId);
+        pimCopyObjectToObject(dObj->pimObjId, (*inputObjBuf)[j + 3]->pimObjId);
 
     }
     pimFree(aObj->pimObjId);
@@ -2095,17 +1854,17 @@ void aesMixColumnsInv(std::vector<PIMAuxilary*>* inputObjBuf) {
 
     for (int j = 0; j < 16; j += 4){
         // a = buf[j];
-        pimCopyDeviceToDevice((*inputObjBuf).data()[j]->pimObjId, aObj->pimObjId);
+        pimCopyObjectToObject((*inputObjBuf).data()[j]->pimObjId, aObj->pimObjId);
         // aObj->pimObjId = (*inputObjBuf).data()[j]->pimObjId;
 
         // b = buf[j + 1];
-        pimCopyDeviceToDevice((*inputObjBuf).data()[j + 1]->pimObjId, bObj->pimObjId);
+        pimCopyObjectToObject((*inputObjBuf).data()[j + 1]->pimObjId, bObj->pimObjId);
 
         // c = buf[j + 2];
-        pimCopyDeviceToDevice((*inputObjBuf).data()[j + 2]->pimObjId, cObj->pimObjId);
+        pimCopyObjectToObject((*inputObjBuf).data()[j + 2]->pimObjId, cObj->pimObjId);
 
         // d = buf[j + 3];
-        pimCopyDeviceToDevice((*inputObjBuf).data()[j + 3]->pimObjId, dObj->pimObjId);
+        pimCopyObjectToObject((*inputObjBuf).data()[j + 3]->pimObjId, dObj->pimObjId);
               
         // e = a ^ b;
         pimXor(aObj->pimObjId, bObj->pimObjId, eObj->pimObjId);
@@ -2166,7 +1925,7 @@ void aesMixColumnsInv(std::vector<PIMAuxilary*>* inputObjBuf) {
         pimXor((*inputObjBuf)[j]->pimObjId, tObj->pimObjId, t0Obj->pimObjId);
 
         // buf[j] = t0;
-        pimCopyDeviceToDevice(t0Obj->pimObjId, (*inputObjBuf)[j]->pimObjId);
+        pimCopyObjectToObject(t0Obj->pimObjId, (*inputObjBuf)[j]->pimObjId);
 
 
         // t0 = b ^ c;
@@ -2182,7 +1941,7 @@ void aesMixColumnsInv(std::vector<PIMAuxilary*>* inputObjBuf) {
         pimXor((*inputObjBuf)[j + 1]->pimObjId, t0Obj->pimObjId, tObj->pimObjId);
 
         // buf[j + 1] = t;
-        pimCopyDeviceToDevice(tObj->pimObjId, (*inputObjBuf)[j + 1]->pimObjId);
+        pimCopyObjectToObject(tObj->pimObjId, (*inputObjBuf)[j + 1]->pimObjId);
 
  
         // t0 = c ^ d;
@@ -2198,7 +1957,7 @@ void aesMixColumnsInv(std::vector<PIMAuxilary*>* inputObjBuf) {
         pimXor((*inputObjBuf)[j + 2]->pimObjId, t0Obj->pimObjId, tObj->pimObjId);
 
         // buf[j + 2] = t;
-        pimCopyDeviceToDevice(tObj->pimObjId, (*inputObjBuf)[j + 2]->pimObjId);
+        pimCopyObjectToObject(tObj->pimObjId, (*inputObjBuf)[j + 2]->pimObjId);
 
 
         // t0 = d ^ a;
@@ -2214,7 +1973,7 @@ void aesMixColumnsInv(std::vector<PIMAuxilary*>* inputObjBuf) {
         pimXor((*inputObjBuf)[j + 3]->pimObjId, t0Obj->pimObjId, tObj->pimObjId);
 
         // buf[j + 3] = t;
-        pimCopyDeviceToDevice(tObj->pimObjId, (*inputObjBuf)[j + 3]->pimObjId);
+        pimCopyObjectToObject(tObj->pimObjId, (*inputObjBuf)[j + 3]->pimObjId);
     }
     pimFree(aObj->pimObjId);
     pimFree(bObj->pimObjId);
@@ -2315,13 +2074,13 @@ void aes256EncryptEcb(std::vector<PIMAuxilary*>* inputObjBuf, unsigned long offs
     std::vector<PIMAuxilary*> *bufTObjBuf = new std::vector<PIMAuxilary*>(AES_BLOCK_SIZE);
     for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
         (*bufTObjBuf)[j]= new PIMAuxilary((*inputObjBuf)[j + offset]);
-        pimCopyDeviceToDevice((*inputObjBuf)[j + offset]->pimObjId, (*bufTObjBuf)[j]->pimObjId);
+        pimCopyObjectToObject((*inputObjBuf)[j + offset]->pimObjId, (*bufTObjBuf)[j]->pimObjId);
     }
 
     std::vector<PIMAuxilary*> *bufT0ObjBuf = new std::vector<PIMAuxilary*>(AES_BLOCK_SIZE);
     for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
         (*bufT0ObjBuf)[j]= new PIMAuxilary((*inputObjBuf)[j + offset]);
-        pimCopyDeviceToDevice((*inputObjBuf)[j + offset]->pimObjId, (*bufT0ObjBuf)[j]->pimObjId);
+        pimCopyObjectToObject((*inputObjBuf)[j + offset]->pimObjId, (*bufT0ObjBuf)[j]->pimObjId);
     }
 
     std::vector<PIMAuxilary*>* keyObjBuf = new std::vector<PIMAuxilary*>(32);
@@ -2358,7 +2117,7 @@ void aes256EncryptEcb(std::vector<PIMAuxilary*>* inputObjBuf, unsigned long offs
             aesAddRoundKey(bufT0ObjBuf, cpkObjBuf, bufTObjBuf);
         }
         for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
-            pimCopyDeviceToDevice((*bufTObjBuf)[j]->pimObjId, (*bufT0ObjBuf)[j]->pimObjId);
+            pimCopyObjectToObject((*bufTObjBuf)[j]->pimObjId, (*bufT0ObjBuf)[j]->pimObjId);
         }
     }
     aesSubBytes(bufTObjBuf);
@@ -2370,7 +2129,7 @@ void aes256EncryptEcb(std::vector<PIMAuxilary*>* inputObjBuf, unsigned long offs
     }
     aesAddRoundKey(bufTObjBuf, cpkObjBuf, bufT0ObjBuf);
     for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
-        pimCopyDeviceToDevice((*bufT0ObjBuf)[j]->pimObjId, (*inputObjBuf)[j + offset]->pimObjId);
+        pimCopyObjectToObject((*bufT0ObjBuf)[j]->pimObjId, (*inputObjBuf)[j + offset]->pimObjId);
     }
 
     for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
@@ -2397,7 +2156,7 @@ void aes256DecryptEcb(std::vector<PIMAuxilary*>* inputObjBuf, unsigned long offs
     std::vector<PIMAuxilary*> *bufTObjBuf = new std::vector<PIMAuxilary*>(AES_BLOCK_SIZE);
     for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
         (*bufTObjBuf)[j]= new PIMAuxilary((*inputObjBuf)[j + offset]);
-        status = pimCopyDeviceToDevice((*inputObjBuf)[j + offset]->pimObjId, (*bufTObjBuf)[j]->pimObjId);
+        status = pimCopyObjectToObject((*inputObjBuf)[j + offset]->pimObjId, (*bufTObjBuf)[j]->pimObjId);
         // status = pimCopyHostToDevice((void*)(*inputObjBuf)[j + offset]->array.data(), (*bufTObjBuf)[j]->pimObjId);
         assert(status == PIM_OK);
     }
@@ -2406,7 +2165,7 @@ void aes256DecryptEcb(std::vector<PIMAuxilary*>* inputObjBuf, unsigned long offs
     for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
         (*bufT0ObjBuf)[j]= new PIMAuxilary((*inputObjBuf)[0]);
         // status = pimCopyHostToDevice((void*)(*inputObjBuf)[j + offset]->array.data(), (*bufT0ObjBuf)[j]->pimObjId);
-        status = pimCopyDeviceToDevice((*inputObjBuf)[j + offset]->pimObjId, (*bufT0ObjBuf)[j]->pimObjId);
+        status = pimCopyObjectToObject((*inputObjBuf)[j + offset]->pimObjId, (*bufT0ObjBuf)[j]->pimObjId);
         assert(status == PIM_OK);
     }
 
@@ -2459,7 +2218,7 @@ void aes256DecryptEcb(std::vector<PIMAuxilary*>* inputObjBuf, unsigned long offs
     aesAddRoundKey(bufT0ObjBuf, cpkObjBuf, bufTObjBuf);
 
     for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
-        pimCopyDeviceToDevice((*bufTObjBuf)[j]->pimObjId, (*inputObjBuf)[j + offset]->pimObjId);
+        pimCopyObjectToObject((*bufTObjBuf)[j]->pimObjId, (*inputObjBuf)[j + offset]->pimObjId);
     }
 
     for (unsigned j = 0; j < AES_BLOCK_SIZE; ++j) {
@@ -2472,26 +2231,19 @@ void aes256DecryptEcb(std::vector<PIMAuxilary*>* inputObjBuf, unsigned long offs
     }
 }
 
-void encryptdemo(uint8_t key[32], std::vector<PIMAuxilary*>* inputObjBuf, unsigned long numCalls) {
+void encryptdemo(uint8_t key[32], std::vector<PIMAuxilary*>* inputObjBuf) {
     aes256Init(key);
     unsigned long offset = 0;
-    for (unsigned long  pass = 0; pass < numCalls; ++pass) {
-        aes256EncryptEcb(inputObjBuf, offset);
-        offset += AES_BLOCK_SIZE;
-    }
+    aes256EncryptEcb(inputObjBuf, offset);
 }
 
-void decryptdemo(uint8_t key[32], std::vector<PIMAuxilary*>* inputObjBuf, unsigned long numCalls) {
+void decryptdemo(uint8_t key[32], std::vector<PIMAuxilary*>* inputObjBuf) {
     aes256Init(key);
     unsigned long offset = 0;
-
-    for (unsigned long  pass = 0; pass < numCalls; ++pass) {
-        aes256DecryptEcb(inputObjBuf, offset);
-        offset += AES_BLOCK_SIZE;
-    }
+    aes256DecryptEcb(inputObjBuf, offset);
 }
 
-int compare_files(const char *file1, const char *file2) {
+int compareFiles(const char *file1, const char *file2) {
     FILE *f1 = fopen(file1, "r");
     FILE *f2 = fopen(file2, "r");
     if (f1 == NULL || f2 == NULL) {
@@ -2530,15 +2282,16 @@ void usage() {
         "\n    -i    input file containing AES encrption input(default=generates input with random numbers)"
         "\n    -c    cipher file containing AES encryption output (default=./cipher.txt)"
         "\n    -o    output file containing AES decryption output (default=./output.txt)"
+        "\n    -d    PIMeval configuration file (default=NULL)"
         "\n    -v    (true/false) validates if the input file and outputfile are the same. (default=false)"
         "\n");
 }
 
 struct Params getInputParams(int argc, char **argv) {
-    struct Params p = {65536, NULL, NULL, "./cipher.txt", "./output.txt", false};
+    struct Params p = {65536, NULL, NULL, "./cipher.txt", "./output.txt", NULL, false};
     int opt;
 
-    while ((opt = getopt(argc, argv, "hl:k:i:c:o:v:")) >= 0) {
+    while ((opt = getopt(argc, argv, "hl:k:i:c:o:v:d:")) >= 0) {
         switch (opt) {
             case 'h':
                 usage();
@@ -2559,6 +2312,9 @@ struct Params getInputParams(int argc, char **argv) {
             case 'o':
                 p.outputFile = optarg;
                 break;
+            case 'd':
+                p.configFile = optarg;
+                break;
             case 'v':
                 p.shouldVerify = (*optarg == 't') ? true : false;
                 break;
@@ -2571,11 +2327,6 @@ struct Params getInputParams(int argc, char **argv) {
     return p;
 }
  
-// Util functions 
-void inline interrupt(int line) { 
-    std::cout << "DEBUG: Interrupted at line " << line << ". \nPress any key to continue ..." << std::endl;
-    int dummy; std::cin >> dummy; 
-} 
 void bufferPadding(uint8_t* inBuf, uint8_t* outBuf, unsigned inNumBytes, unsigned outNumBytes) { 
     // Copy the occupied part of the input buffer to the output buffer
     memcpy(outBuf, inBuf, inNumBytes);
@@ -2585,4 +2336,3 @@ void bufferPadding(uint8_t* inBuf, uint8_t* outBuf, unsigned inNumBytes, unsigne
     memset((void*) (outBuf + inNumBytes), 0, numZeroBytes * sizeof(uint8_t));
 }
 
-
diff --git a/PIMbench/aes/baselines/GPU/Makefile b/PIMbench/aes/baselines/GPU/Makefile
index c789bd18..58131d8f 100644
--- a/PIMbench/aes/baselines/GPU/Makefile
+++ b/PIMbench/aes/baselines/GPU/Makefile
@@ -3,7 +3,7 @@ NVCC := nvcc
 CUDA_DIR ?= $(shell dirname $(shell dirname $(shell which nvcc)))
 CUDA_INCLUDE := $(CUDA_DIR)/include
 CUDA_LIB_DIR := $(CUDA_DIR)/lib64
-CUDA_LIB := -lcublas
+CUDA_LIB := -lcublas -lnvidia-ml
 FOPENMP := -Xcompiler -fopenmp
 ARCH_FLAG := -arch=sm_80
 
@@ -16,13 +16,15 @@ SOURCES := aes.cu
 # Compiler flags
 CFLAGS := -O3
 CXXFLAGS := -std=c++17
+# Dependancy
+DEP := ../../../../util/
 
 # Default target
 all: $(EXEC)
 
 # Rule to build the target executable
 $(EXEC): $(SOURCES)
-	$(NVCC) $(SOURCES) -I$(CUDA_INCLUDE) $(ARCH_FLAG) -L$(CUDA_LIB_DIR) $(CUDA_LIB) $(FOPENMP) $(CFLAGS) $(CXXFLAGS) -o $(EXEC)
+	$(NVCC) $(SOURCES) -DENABLE_CUDA -I$(CUDA_INCLUDE) -I$(DEP) $(ARCH_FLAG) -L$(CUDA_LIB_DIR) $(CUDA_LIB) $(FOPENMP) $(CFLAGS) $(CXXFLAGS) -o $(EXEC)
 
 # Rule to clean the project
 clean:
diff --git a/PIMbench/aes/baselines/GPU/aes.cu b/PIMbench/aes/baselines/GPU/aes.cu
index 73018c98..6be5b3f6 100644
--- a/PIMbench/aes/baselines/GPU/aes.cu
+++ b/PIMbench/aes/baselines/GPU/aes.cu
@@ -9,14 +9,13 @@
 #include <time.h>
 #include <math.h>
 #include <inttypes.h>
-#include <chrono>
 #include <iostream>
 #include <iomanip>
 #include <vector>
 #include <random>
 #include <getopt.h>
 #include <cuda_runtime.h>
-#define MEASUREMENT_TIMES (1 << 4) 
+#include "utilBaselines.h"
 
 uint8_t ctx_key[32]; 
 uint8_t ctx_enckey[32]; 
@@ -108,10 +107,10 @@ __constant__ static const uint8_t sboxinv[256] = {
 typedef struct Params
 {
     uint64_t inputSize;
-    char *keyFile;
-    char *inputFile;
-    char *cipherFile;
-    char *outputFile;
+    const char *keyFile;
+    const char *inputFile;
+    const char *cipherFile;
+    const char *outputFile;
     bool shouldVerify;
 } Params;
 
@@ -284,7 +283,7 @@ __device__ void aes_mixColumns_inv(uint8_t *buf){
 
 // add expand key operation
 __device__ __host__ void aes_expandEncKey(uint8_t *k, uint8_t *rc, const uint8_t *sb){
-  register uint8_t i;
+  uint8_t i;
 
   k[0] ^= sb[k[29]] ^ (*rc);
   k[1] ^= sb[k[30]];
@@ -347,7 +346,7 @@ __device__ void aes_expandDecKey(uint8_t *k, uint8_t *rc){
 // key initition
 void aes256_init(uint8_t *k){
   uint8_t rcon = 1;
-  register uint8_t i;
+  uint8_t i;
 
   for (i = 0; i < sizeof(ctx_key); i++){
     ctx_enckey[i] = ctx_deckey[i] = k[i];
@@ -470,7 +469,24 @@ void encryptdemo(uint8_t key[AES_KEY_BUFFER_SIZE], uint8_t *buf, unsigned long n
 
   dim3 dimBlock(ceil((double)numbytes / (double)(THREADS_PER_BLOCK * AES_BLOCK_SIZE)));
   dim3 dimGrid(THREADS_PER_BLOCK);
-  aes256_encrypt_ecb<<<dimBlock, dimGrid>>>(buf_d, numbytes, ctx_enckey_d, ctx_key_d);
+  auto [timeElapsed, avgPower, energy] = measureCUDAPowerAndElapsedTime([&]() {
+        aes256_encrypt_ecb<<<dimBlock, dimGrid>>>(buf_d, numbytes, ctx_enckey_d, ctx_key_d);
+        cudaDeviceSynchronize(); // ensure all are done
+    });
+
+
+  // Check for kernel launch errors
+  errorCode = cudaGetLastError();
+  if (errorCode != cudaSuccess)
+  {
+      cerr << "Cuda Error: " << cudaGetErrorString(errorCode) << "\n";
+      exit(1);
+  }
+
+  printf("\nINFO: Execution time of encryption = %f ms\n", timeElapsed);
+  printf("INFO: Average Power = %f mW\n", avgPower);
+  printf("INFO: Energy Consumption = %f mJ\n", energy);
+
 
   errorCode = cudaMemcpy(buf, buf_d, numbytes, cudaMemcpyDeviceToHost);
   if (errorCode != cudaSuccess)
@@ -548,7 +564,23 @@ void decryptdemo(uint8_t key[AES_KEY_BUFFER_SIZE], uint8_t *buf, unsigned long n
 
   dim3 dimBlock(ceil((double)numbytes / (double)(THREADS_PER_BLOCK * AES_BLOCK_SIZE)));
   dim3 dimGrid(THREADS_PER_BLOCK);
-  aes256_decrypt_ecb<<<dimBlock, dimGrid>>>(buf_d, numbytes, ctx_deckey_d, ctx_key_d);
+  auto [timeElapsed, avgPower, energy] = measureCUDAPowerAndElapsedTime([&]() {
+        aes256_decrypt_ecb<<<dimBlock, dimGrid>>>(buf_d, numbytes, ctx_deckey_d, ctx_key_d);
+        cudaDeviceSynchronize(); // ensure all are done
+    });
+
+
+  // Check for kernel launch errors
+  errorCode = cudaGetLastError();
+  if (errorCode != cudaSuccess)
+  {
+      cerr << "Cuda Error: " << cudaGetErrorString(errorCode) << "\n";
+      exit(1);
+  }
+
+  printf("\nINFO: Execution time of decryption = %f ms\n", timeElapsed);
+  printf("INFO: Average Power = %f mW\n", avgPower);
+  printf("INFO: Energy Consumption = %f mJ\n", energy);
 
   errorCode = cudaMemcpy(buf, buf_d, numbytes, cudaMemcpyDeviceToHost);
   if (errorCode != cudaSuccess)
@@ -604,13 +636,7 @@ void test_encryptdemo_parallel() {
     aes256_init(key);
 
     // Start encrypt in CPU
-    auto start = std::chrono::high_resolution_clock::now();
-    for (int k = 0; k < MEASUREMENT_TIMES; k++) {
-        encryptdemo(key, buffer.data(), SIZE);
-    }
-    auto end = std::chrono::high_resolution_clock::now();
-    std::chrono::duration<double, std::milli> elapsedTime = (end - start) / MEASUREMENT_TIMES;
-    std::cout << "Encryption Duration: " << std::fixed << std::setprecision(6) << elapsedTime.count() << " ms." << std::endl;
+    encryptdemo(key, buffer.data(), SIZE);
 
 }
 
@@ -639,13 +665,7 @@ void test_decryptdemo_parallel() {
 
 
     // Start encrypt in CPU
-    auto start = std::chrono::high_resolution_clock::now();
-    for (int k = 0; k < MEASUREMENT_TIMES; k++) {
-        decryptdemo(key, buffer.data(), SIZE);
-    }
-    auto end = std::chrono::high_resolution_clock::now();
-    std::chrono::duration<double, std::milli> elapsedTime = (end - start) / MEASUREMENT_TIMES;
-    std::cout << "Decryption Duration: " << std::fixed << std::setprecision(6) << elapsedTime.count() << " ms." << std::endl;
+    decryptdemo(key, buffer.data(), SIZE);
 
 }
 
@@ -653,7 +673,7 @@ int main(int argc, char *argv[]) {
     struct Params params = getInputParams(argc, argv);
     
     FILE *file; uint8_t *buf; 
-    int padding;
+    unsigned long long padding;
     uint8_t key[AES_KEY_BUFFER_SIZE];
     unsigned long long numbytes; 
     int deviceCount = 0;
@@ -735,21 +755,14 @@ int main(int argc, char *argv[]) {
     // Calculate padding.
     padding = AES_BLOCK_SIZE - (numbytes % AES_BLOCK_SIZE);
     numbytes += padding;
-    printf("Padding file with %d bytes for a new size of %lu\n", padding, numbytes);
+    printf("Padding file with %llu bytes for a new size of %llu\n", padding, numbytes);
 
     // This is to force nvcc to put the GPU initialization here.
     GPU_init<<<1, 1>>>();
   
     aes256_init(key);
     // Encryption.
-    auto start = std::chrono::high_resolution_clock::now();
-    for (int k = 0; k < MEASUREMENT_TIMES; k++) {
-        encryptdemo(key, buf, numbytes);
-    }
-    auto end = std::chrono::high_resolution_clock::now();
-    std::chrono::duration<double, std::milli> elapsedTime = (end - start) / MEASUREMENT_TIMES;
-    std::cout << "Encryption Duration: " << std::fixed << std::setprecision(3) << elapsedTime.count() << " ms." << std::endl;
-    std::cout << "GPU encryption throughput: " << std::fixed << std::setprecision(3) << (numbytes / elapsedTime.count() * 1000) << " bytes/second\n";
+    encryptdemo(key, buf, numbytes);
 
     // Write the ciphertext to file
     file = fopen(params.cipherFile, "w");
@@ -762,14 +775,7 @@ int main(int argc, char *argv[]) {
     fclose(file);
 
     // Decryption.
-    start = std::chrono::high_resolution_clock::now();
-    for (int k = 0; k < MEASUREMENT_TIMES; k++) {
-        decryptdemo(key, buf, numbytes);
-    }
-    end = std::chrono::high_resolution_clock::now();
-    elapsedTime = (end - start) / MEASUREMENT_TIMES;
-    std::cout << "Decryption Duration: " << std::fixed << std::setprecision(3) << elapsedTime.count() << " ms." << std::endl;
-    std::cout << "GPU decryption throughput: " << std::fixed << std::setprecision(3) << (numbytes / elapsedTime.count() * 1000) << " bytes/second\n";
+    decryptdemo(key, buf, numbytes);
 
     // Write to output file
     file = fopen(params.outputFile, "w");
diff --git a/libpimeval/src/libpimeval.cpp b/libpimeval/src/libpimeval.cpp
index d1071d27..78ea0c1d 100644
--- a/libpimeval/src/libpimeval.cpp
+++ b/libpimeval/src/libpimeval.cpp
@@ -462,6 +462,22 @@ pimCondSelectScalar(PimObjId condBool, PimObjId src1, uint64_t scalarBits, PimOb
   return ok ? PIM_OK : PIM_ERROR;
  }
 
+//! @brief  AES Sbox: dest[i] = lut[src[i]]
+PimStatus 
+pimAesSbox(PimObjId src, PimObjId dest, const std::vector<uint8_t>& lut)
+{
+  bool ok = pimSim::get()->pimAesSbox(src, dest, lut);
+  return ok ? PIM_OK : PIM_ERROR;
+}
+
+//! @brief  AES Sbox: dest[i] = lut[src[i]] (similar to AES sbox, different in perforamance and energy model for the bit-serial architecture)
+PimStatus 
+pimAesInverseSbox(PimObjId src, PimObjId dest, const std::vector<uint8_t>& lut)
+{
+  bool ok = pimSim::get()->pimAesInverseSbox(src, dest, lut);
+  return ok ? PIM_OK : PIM_ERROR;
+}
+
 // Implementation of min reduction
 PimStatus pimRedMin(PimObjId src, void* min, uint64_t idxBegin, uint64_t idxEnd) {
     bool ok = pimSim::get()->pimRedMin(src, min, idxBegin, idxEnd);
diff --git a/libpimeval/src/libpimeval.h b/libpimeval/src/libpimeval.h
index efb735f6..ec5a8c75 100644
--- a/libpimeval/src/libpimeval.h
+++ b/libpimeval/src/libpimeval.h
@@ -198,6 +198,12 @@ PimStatus pimShiftElementsLeft(PimObjId src);
 PimStatus pimShiftBitsRight(PimObjId src, PimObjId dest, unsigned shiftAmount);
 PimStatus pimShiftBitsLeft(PimObjId src, PimObjId dest, unsigned shiftAmount);
 
+// AES sbox and inverse-box APIs
+// Note: AES S-box and inverse S-box are treated separately because their bit-serial performance models differ.
+// However, it is the user's responsibility to provide the appropriate LUT to ensure correct functionality.
+// The function pimAesInverseSbox expects an inverse S-box LUT as its input.
+PimStatus pimAesSbox(PimObjId src, PimObjId dest, const std::vector<uint8_t>& lut); 
+PimStatus pimAesInverseSbox(PimObjId src, PimObjId dest, const std::vector<uint8_t>& lut); 
 
 ////////////////////////////////////////////////////////////////////////////////
 // Experimental Feature: PIM API Fusion                                       //
diff --git a/libpimeval/src/pimCmd.cpp b/libpimeval/src/pimCmd.cpp
index 6f6c49af..11a37ab9 100644
--- a/libpimeval/src/pimCmd.cpp
+++ b/libpimeval/src/pimCmd.cpp
@@ -69,6 +69,8 @@ pimCmd::getName(PimCmdEnum cmdType, const std::string& suffix)
     { PimCmdEnum::COND_BROADCAST, "cond_broadcast" },
     { PimCmdEnum::COND_SELECT, "cond_select" },
     { PimCmdEnum::COND_SELECT_SCALAR, "cond_select_scalar" },
+    { PimCmdEnum::AES_SBOX, "aes_sbox" },
+    { PimCmdEnum::AES_INVERSE_SBOX, "aes_inverse_sbox" },
     { PimCmdEnum::REDSUM, "redsum" },
     { PimCmdEnum::REDSUM_RANGE, "redsum_range" },
     { PimCmdEnum::REDMIN, "redmin" },
@@ -449,6 +451,18 @@ pimCmdFunc1::sanityCheck() const
         return false;
       }
       break;
+    case PimCmdEnum::AES_SBOX:
+    case PimCmdEnum::AES_INVERSE_SBOX:
+      if (objSrc.getDataType() != PIM_UINT8) {
+        return false;
+      }
+      if (objDest.getDataType() != PIM_UINT8) {
+        return false;
+      }
+      if (m_lut.size() != 256) {
+        return false;
+      }
+      break;
     default:
       if (objSrc.getDataType() != objDest.getDataType()) {
         std::printf("PIM-Error: PIM command %s does not support mixed data type\n", getName().c_str());
@@ -931,7 +945,7 @@ pimCmdCond::updateStats() const
   pimSim::get()->getStatsMgr()->recordCmd(getName(dataType, isVLayout), mPerfEnergy);
   return true;
 }
-
+ 
 //! @brief  PIM CMD: redsum non-ranged/ranged - sanity check
 template <typename T> bool
 pimCmdReduction<T>::sanityCheck() const
diff --git a/libpimeval/src/pimCmd.h b/libpimeval/src/pimCmd.h
index 0b05fd1f..d08a046c 100644
--- a/libpimeval/src/pimCmd.h
+++ b/libpimeval/src/pimCmd.h
@@ -83,6 +83,8 @@ enum class PimCmdEnum {
   ROTATE_ELEM_L,
   SHIFT_ELEM_R,
   SHIFT_ELEM_L,
+  AES_SBOX,
+  AES_INVERSE_SBOX, 
 
   // BitSIMD v-layout commands
   ROW_R,
@@ -208,6 +210,8 @@ class pimCmdFunc1 : public pimCmd
 public:
   pimCmdFunc1(PimCmdEnum cmdType, PimObjId src, PimObjId dest, uint64_t scalarValue = 0)
     : pimCmd(cmdType), m_src(src), m_dest(dest), m_scalarValue(scalarValue) {}
+  pimCmdFunc1(PimCmdEnum cmdType, PimObjId src, PimObjId dest, const std::vector<uint8_t>& lut)
+    : pimCmd(cmdType), m_src(src), m_dest(dest), m_lut(lut) {}
   virtual ~pimCmdFunc1() {}
   virtual bool execute() override;
   virtual bool sanityCheck() const override;
@@ -217,6 +221,7 @@ class pimCmdFunc1 : public pimCmd
   PimObjId m_src;
   PimObjId m_dest;
   uint64_t m_scalarValue;
+  std::vector<uint8_t> m_lut; 
 private:
   template<typename T>
   inline bool computeResult(T operand, PimCmdEnum cmdType, T scalarValue, T& result, int bitsPerElementSrc) {
@@ -266,6 +271,10 @@ class pimCmdFunc1 : public pimCmd
         }
         break;
     }
+    case PimCmdEnum::AES_SBOX:
+    case PimCmdEnum::AES_INVERSE_SBOX:
+      result = m_lut[operand]; 
+      break;
     default:
         std::printf("PIM-Error: Unexpected cmd type %d\n", static_cast<int>(cmdType));
         assert(0);
diff --git a/libpimeval/src/pimPerfEnergyAquabolt.cpp b/libpimeval/src/pimPerfEnergyAquabolt.cpp
index c1f4ff4f..8b111004 100644
--- a/libpimeval/src/pimPerfEnergyAquabolt.cpp
+++ b/libpimeval/src/pimPerfEnergyAquabolt.cpp
@@ -38,6 +38,11 @@ pimPerfEnergyAquabolt::getPerfEnergyForFunc1(PimCmdEnum cmdType, const pimObjInf
   uint64_t totalOp = 0;
   switch (cmdType)
   {
+    case PimCmdEnum::AES_SBOX:
+    case PimCmdEnum::AES_INVERSE_SBOX:
+    {
+      numberOfOperationPerElement = 1; // Assuming each 8-bit element allocates one ALU word 
+    }
     case PimCmdEnum::ADD_SCALAR:
     case PimCmdEnum::MUL_SCALAR:
     { 
@@ -249,3 +254,4 @@ pimPerfEnergyAquabolt::getPerfEnergyForRotate(PimCmdEnum cmdType, const pimObjIn
 
   return pimeval::perfEnergy(msRuntime, mjEnergy, msRead, msWrite, msCompute, totalOp);
 }
+
diff --git a/libpimeval/src/pimPerfEnergyAquabolt.h b/libpimeval/src/pimPerfEnergyAquabolt.h
index d3f10b39..5dcca145 100644
--- a/libpimeval/src/pimPerfEnergyAquabolt.h
+++ b/libpimeval/src/pimPerfEnergyAquabolt.h
@@ -27,7 +27,7 @@ class pimPerfEnergyAquabolt : public pimPerfEnergyBase
   virtual pimeval::perfEnergy getPerfEnergyForReduction(PimCmdEnum cmdType, const pimObjInfo& obj, unsigned numPass) const override;
   virtual pimeval::perfEnergy getPerfEnergyForBroadcast(PimCmdEnum cmdType, const pimObjInfo& obj) const override;
   virtual pimeval::perfEnergy getPerfEnergyForRotate(PimCmdEnum cmdType, const pimObjInfo& obj) const override;
-
+  
 protected:
   unsigned m_aquaboltFPUBitWidth = 16;
   // TODO: Update for Aquabolt
diff --git a/libpimeval/src/pimPerfEnergyBankLevel.cpp b/libpimeval/src/pimPerfEnergyBankLevel.cpp
index 3da368eb..69e7af36 100644
--- a/libpimeval/src/pimPerfEnergyBankLevel.cpp
+++ b/libpimeval/src/pimPerfEnergyBankLevel.cpp
@@ -80,6 +80,18 @@ pimPerfEnergyBankLevel::getPerfEnergyForFunc1(PimCmdEnum cmdType, const pimObjIn
       totalOp = obj.getNumElements();
       break;
     }
+    case PimCmdEnum::AES_SBOX:
+    case PimCmdEnum::AES_INVERSE_SBOX:
+    {
+      // NOTE:
+      // Although the Processing Element (PE) is 32 bits wide and can theoretically perform four 8-bit operations in parallel,
+      // in the case of these LUT-based commands (e.g., AES S-box or inverse S-box), each operation is treated as a single,
+      // independent access driven by an 8-bit input.
+      //
+      // If the operation instead made full use of the 32-bit PE width to process four 8-bit inputs in parallel
+      // then numberOfOperationPerElement would be 0.25. However, such parallelism is not modeled here due to the limitation of the LUT.
+      numberOfOperationPerElement = 1;
+    }
     case PimCmdEnum::AND_SCALAR:
     case PimCmdEnum::OR_SCALAR:
     case PimCmdEnum::XOR_SCALAR:
@@ -103,6 +115,10 @@ pimPerfEnergyBankLevel::getPerfEnergyForFunc1(PimCmdEnum cmdType, const pimObjIn
       mjEnergy += (m_eW * maxGDLItr * (numPass-1) * numBankPerChip + (m_eW * minGDLItr * numBankPerChip));
       mjEnergy += m_pBChip * m_numChipsPerRank * m_numRanks * msRuntime;
       totalOp = obj.getNumElements();
+      //
+      // Therefore, for the uint8 data type, we set numberOfOperationPerElement = 1 because each 8-bit input
+      // corresponds to one logical LUT access, and we assume that this access is not vectorized across multiple inputs
+      // within a single PE execution. In other words, we model the cost at the granularity of one element per operation.
       break;
     }
     default:
diff --git a/libpimeval/src/pimPerfEnergyFulcrum.cpp b/libpimeval/src/pimPerfEnergyFulcrum.cpp
index 55034757..532ccb69 100644
--- a/libpimeval/src/pimPerfEnergyFulcrum.cpp
+++ b/libpimeval/src/pimPerfEnergyFulcrum.cpp
@@ -126,6 +126,12 @@ pimPerfEnergyFulcrum::getPerfEnergyForFunc1(PimCmdEnum cmdType, const pimObjInfo
       totalOp = obj.getNumElements();
       break;
     }
+    case PimCmdEnum::AES_SBOX:
+    case PimCmdEnum::AES_INVERSE_SBOX:
+      msRuntime = 1e10;
+      mjEnergy = 999999999.9;
+      std::cout << "PIM-Warning: Perf energy model not available for PIM command " << pimCmd::getName(cmdType, "") << std::endl;
+      break;
     default:
       std::cout << "PIM-Warning: Perf energy model not available for PIM command " << pimCmd::getName(cmdType, "") << std::endl;
       break;
diff --git a/libpimeval/src/pimPerfEnergyTables.cpp b/libpimeval/src/pimPerfEnergyTables.cpp
index f97a3844..a28aa648 100644
--- a/libpimeval/src/pimPerfEnergyTables.cpp
+++ b/libpimeval/src/pimPerfEnergyTables.cpp
@@ -211,6 +211,8 @@ pimPerfEnergyTables::bitsimdPerfTable = {
       { PimCmdEnum::REDMIN_RANGE, {   32,    8,   42 } },
       { PimCmdEnum::REDMAX,       {   32,    8,   42 } },
       { PimCmdEnum::REDMAX_RANGE, {   32,    8,   42 } },
+      { PimCmdEnum::AES_SBOX,     {   2450, 2112, 3625 } },
+      { PimCmdEnum::AES_INVERSE_SBOX,     {   2420, 2086, 3610 } },
     }},
     { PIM_UINT16, {
       { PimCmdEnum::ABS,          {   16,   16,    0 } },
@@ -531,6 +533,8 @@ pimPerfEnergyTables::bitsimdPerfTable = {
       { PimCmdEnum::REDMIN_RANGE, {   32,    8,   51 } },
       { PimCmdEnum::REDMAX,       {   32,    8,   51 } },
       { PimCmdEnum::REDMAX_RANGE, {   32,    8,   51 } },
+      { PimCmdEnum::AES_SBOX,     {   2450, 2112, 3625 } },
+      { PimCmdEnum::AES_INVERSE_SBOX,     {   2420, 2086, 3610 } },
     }},
     { PIM_UINT16, {
       { PimCmdEnum::ABS,          {   16,   16,    0 } },
diff --git a/libpimeval/src/pimSim.cpp b/libpimeval/src/pimSim.cpp
index d771ccde..2f80b2f3 100644
--- a/libpimeval/src/pimSim.cpp
+++ b/libpimeval/src/pimSim.cpp
@@ -875,6 +875,24 @@ pimSim::pimShiftBitsLeft(PimObjId src, PimObjId dest, unsigned shiftAmount)
   return m_device->executeCmd(std::move(cmd));
 }
 
+bool 
+pimSim::pimAesSbox(PimObjId src, PimObjId dest, const std::vector<uint8_t>& lut)
+{
+  pimPerfMon perfMon("pimAesSbox");
+  if (!isValidDevice()) { return false; }
+  std::unique_ptr<pimCmd> cmd = std::make_unique<pimCmdFunc1>(PimCmdEnum::AES_SBOX, src, dest, lut);
+  return m_device->executeCmd(std::move(cmd));
+}
+
+bool 
+pimSim::pimAesInverseSbox(PimObjId src, PimObjId dest, const std::vector<uint8_t>& lut)
+{
+  pimPerfMon perfMon("pimAesInverseSbox");
+  if (!isValidDevice()) { return false; }
+  std::unique_ptr<pimCmd> cmd = std::make_unique<pimCmdFunc1>(PimCmdEnum::AES_INVERSE_SBOX, src, dest, lut);
+  return m_device->executeCmd(std::move(cmd));
+}
+
 bool
 pimSim::pimFuse(PimProg prog)
 {
diff --git a/libpimeval/src/pimSim.h b/libpimeval/src/pimSim.h
index bc97b8e9..c0fe0d7c 100644
--- a/libpimeval/src/pimSim.h
+++ b/libpimeval/src/pimSim.h
@@ -124,6 +124,8 @@ class pimSim
   bool pimShiftElementsLeft(PimObjId src);
   bool pimShiftBitsRight(PimObjId src, PimObjId dest, unsigned shiftAmount);
   bool pimShiftBitsLeft(PimObjId src, PimObjId dest, unsigned shiftAmount);
+  bool pimAesSbox(PimObjId src, PimObjId dest, const std::vector<uint8_t>& lut); 
+  bool pimAesInverseSbox(PimObjId src, PimObjId dest, const std::vector<uint8_t>& lut); 
 
   // PIM API Fusion
   bool pimFuse(PimProg prog);
diff --git a/tests/test-aes-sbox/Makefile b/tests/test-aes-sbox/Makefile
new file mode 100644
index 00000000..87702f5f
--- /dev/null
+++ b/tests/test-aes-sbox/Makefile
@@ -0,0 +1,18 @@
+# Makefile: Test reduction min
+# Copyright (c) 2024 University of Virginia
+# This file is licensed under the MIT License.
+# See the LICENSE file in the root of this repository for more details.
+
+PROJ_ROOT = ../..
+include ${PROJ_ROOT}/Makefile.common
+
+EXEC := test-aes-sbox.out
+SRC := test-aes-sbox.cpp
+
+debug perf dramsim3_integ: $(EXEC)
+
+$(EXEC): $(SRC) $(DEPS)
+	$(CXX) $< $(CXXFLAGS) -o $@
+
+clean:
+	rm -rf $(EXEC) *.dSYM
diff --git a/tests/test-aes-sbox/test-aes-sbox.cpp b/tests/test-aes-sbox/test-aes-sbox.cpp
new file mode 100644
index 00000000..c481bb19
--- /dev/null
+++ b/tests/test-aes-sbox/test-aes-sbox.cpp
@@ -0,0 +1,163 @@
+// Test: Test reduction min
+// Copyright (c) 2024 University of Virginia
+// This file is licensed under the MIT License.
+// See the LICENSE file in the root of this repository for more details.
+
+#include "libpimeval.h"
+#include <iostream>
+#include <vector>
+#include <cassert>
+#include <cstdlib>
+#include <cstdint>
+#include <limits> // Include this for UINT_MAX
+                  //
+// Forward S-box table for SubBytes step in AES encryption algorithm.
+const uint8_t sbox[256] = {
+  0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
+    0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
+    0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
+    0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
+    0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
+    0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
+    0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
+    0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
+    0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
+    0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
+    0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
+    0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
+    0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
+    0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
+    0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
+    0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
+    0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
+    0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
+    0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
+    0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
+    0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
+    0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
+    0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
+    0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
+    0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
+    0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
+    0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
+    0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
+    0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
+    0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
+    0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
+    0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
+};
+// Inverse S-box table for SubBytesInverse step in AES dectyption algorithm.
+const uint8_t sboxinv[256] = {
+    0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38,
+    0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
+    0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87,
+    0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
+    0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d,
+    0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
+    0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2,
+    0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
+    0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16,
+    0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
+    0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda,
+    0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
+    0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a,
+    0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
+    0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02,
+    0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
+    0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea,
+    0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
+    0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85,
+    0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
+    0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89,
+    0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
+    0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20,
+    0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
+    0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31,
+    0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
+    0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d,
+    0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
+    0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0,
+    0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
+    0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26,
+    0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
+};
+
+bool testAesSboxOrInverseSbox(PimDeviceEnum deviceType, bool isForward = true)
+{ 
+  std::vector<uint8_t> lut_vec;
+  unsigned numRanks = 2;
+  unsigned numBankPerRank = 2;
+  unsigned numSubarrayPerBank = 8;
+  unsigned numRows = 1024;
+  unsigned numCols = 8192;
+
+  uint64_t numElements = 256;
+  std::vector<uint8_t> src(numElements);
+  std::vector<uint8_t> dest(numElements);
+
+  for (uint64_t i = 0; i < numElements; ++i) {
+    src[i] = i;
+  }
+
+  PimStatus status = pimCreateDevice(deviceType, numRanks, numBankPerRank, numSubarrayPerBank, numRows, numCols);
+  assert(status == PIM_OK);
+  
+  bool ok = true;
+  PimObjId srcObj = pimAlloc(PIM_ALLOC_AUTO, numElements, PIM_UINT8);
+  assert(srcObj != -1);
+
+  PimObjId destObj = pimAllocAssociated(srcObj, PIM_UINT8);
+  assert(destObj != -1);
+
+  status = pimCopyHostToDevice((void*)src.data(), srcObj);
+  assert(status == PIM_OK);
+
+  if (isForward) {
+    lut_vec = std::vector<uint8_t>(std::begin(sbox), std::end(sbox));
+    status = pimAesSbox(srcObj, destObj, lut_vec);
+    assert(status == PIM_OK);
+  }
+  else {
+    lut_vec = std::vector<uint8_t>(std::begin(sboxinv), std::end(sboxinv));
+    status = pimAesInverseSbox(srcObj, destObj, lut_vec);
+    assert(status == PIM_OK);
+  }
+
+  status = pimCopyDeviceToHost(destObj, (void*)dest.data()); 
+  assert(status == PIM_OK);
+
+  for (uint64_t i = 0; i < numElements; ++i) {
+    if (lut_vec[src[i]] != dest[i]) { 
+      std::cout << "Failed!" << std::endl;
+      ok = false;
+      break;
+    }
+  }
+
+  pimFree(srcObj);
+  pimFree(destObj);
+
+  pimShowStats();
+  pimResetStats();
+  pimDeleteDevice();
+  return ok;
+}
+
+int main()
+{
+  std::cout << "INFO: PIM AES sbox and inverse sbox test" << std::endl;
+
+  bool ok = true;
+  ok &= testAesSboxOrInverseSbox(PIM_DEVICE_BITSIMD_V);
+  ok &= testAesSboxOrInverseSbox(PIM_DEVICE_BITSIMD_V_AP);
+  ok &= testAesSboxOrInverseSbox(PIM_DEVICE_BANK_LEVEL);
+  ok &= testAesSboxOrInverseSbox(PIM_DEVICE_FULCRUM);
+  ok &= testAesSboxOrInverseSbox(PIM_DEVICE_BITSIMD_V, false);
+  ok &= testAesSboxOrInverseSbox(PIM_DEVICE_BITSIMD_V_AP, false);
+  ok &= testAesSboxOrInverseSbox(PIM_DEVICE_BANK_LEVEL, false);
+  ok &= testAesSboxOrInverseSbox(PIM_DEVICE_FULCRUM, false);
+  std::cout << (ok ? "ALL PASSED!" : "FAILED!") << std::endl;
+
+
+  return 0;
+}


Reviews:
user: login: fasiddique
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-02T13:53:38Z
user: login: deyuan
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-06T07:04:33Z
user: login: deyuan
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-06T07:05:20Z
user: login: deyuan
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-06T07:06:24Z
user: login: fasiddique
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-06T14:13:16Z
user: login: fasiddique
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-06T14:13:34Z
user: login: fasiddique
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-06T14:15:20Z
user: login: mhgholamrezaei
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T03:07:27Z
user: login: mhgholamrezaei
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T03:25:37Z
user: login: mhgholamrezaei
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T03:25:44Z
user: login: mhgholamrezaei
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T03:33:31Z
user: login: deyuan
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T07:36:43Z
user: login: deyuan
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T07:39:24Z
user: login: deyuan
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T07:41:06Z
user: login: deyuan
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-07T07:41:31Z
user: login: mhgholamrezaei
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T13:42:26Z
user: login: mhgholamrezaei
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T13:42:50Z

Review comments:
diff_hunk: @@ -326,3 +326,52 @@ pimPerfEnergyBankLevel::getPerfEnergyForRotate(PimCmdEnum cmdType, const pimObjI
   return pimeval::perfEnergy(msRuntime, mjEnergy, msRead, msWrite, msCompute, totalOp);
 }
 
+//! @brief  Perf energy model of bank-level PIM for func lut
+pimeval::perfEnergy
+pimPerfEnergyBankLevel::getPerfEnergyForLut(PimCmdEnum cmdType, const pimObjInfo& obj, const pimObjInfo& objDest) const
+{
+  double msRuntime = 0.0;
+  double mjEnergy = 0.0;
+  double msRead = 0.0;
+  double msWrite = 0.0;
+  double msCompute = 0.0;
+  unsigned numPass = obj.getMaxNumRegionsPerCore();
+  unsigned bitsPerElement = obj.getBitsPerElement(PimBitWidth::ACTUAL);
+  uint64_t totalOp = 0;
+  unsigned numCores = obj.isLoadBalanced() ? obj.getNumCoreAvailable() : obj.getNumCoresUsed();
+
+  unsigned maxElementsPerRegion = obj.getMaxElementsPerRegion();
+  double numberOfOperationPerElement = 1;  // Assuming each 8-bit element allocates one ALU word 
path: libpimeval/src/pimPerfEnergyBankLevel.cpp
user: login: fasiddique
body: I think it would be better to not hard code this. 
created_at: 2025-05-02T13:53:38Z
author_association: COLLABORATOR
diff_hunk: @@ -266,6 +271,10 @@ class pimCmdFunc1 : public pimCmd
         }
         break;
     }
+    case PimCmdEnum::AES_SBOX:
+    case PimCmdEnum::AES_INVERSE_SBOX:
+      result = m_lut[operand]; 
path: libpimeval/src/pimCmd.h
user: login: deyuan
body: For inverse s-box, do you expect we do inverse lookup or just pass in the inverse lut? If with the latter, a single AES_SBOX command would be sufficient
created_at: 2025-05-06T07:04:32Z
author_association: COLLABORATOR
diff_hunk: @@ -27,7 +27,7 @@ class pimPerfEnergyAquabolt : public pimPerfEnergyBase
   virtual pimeval::perfEnergy getPerfEnergyForReduction(PimCmdEnum cmdType, const pimObjInfo& obj, unsigned numPass) const override;
   virtual pimeval::perfEnergy getPerfEnergyForBroadcast(PimCmdEnum cmdType, const pimObjInfo& obj) const override;
   virtual pimeval::perfEnergy getPerfEnergyForRotate(PimCmdEnum cmdType, const pimObjInfo& obj) const override;
-
+  
path: libpimeval/src/pimPerfEnergyAquabolt.h
user: login: deyuan
body: Could you revert some line touching with trailing spaces?
created_at: 2025-05-06T07:05:20Z
author_association: COLLABORATOR
diff_hunk: @@ -9,7 +9,7 @@
 
 #include <cstdint>
 #include <cstdarg>
-
+#include <vector> 
path: libpimeval/src/libpimeval.h
user: login: deyuan
body: Need to keep an empty line
created_at: 2025-05-06T07:06:23Z
author_association: COLLABORATOR
diff_hunk: @@ -211,6 +211,8 @@ pimPerfEnergyTables::bitsimdPerfTable = {
       { PimCmdEnum::REDMIN_RANGE, {   32,    8,   42 } },
       { PimCmdEnum::REDMAX,       {   32,    8,   42 } },
       { PimCmdEnum::REDMAX_RANGE, {   32,    8,   42 } },
+      { PimCmdEnum::AES_SBOX,     {   2450, 2112, 3625 } },
+      { PimCmdEnum::AES_INVERSE_SBOX,     {   2420, 2086, 3610 } },
path: libpimeval/src/pimPerfEnergyTables.cpp
user: login: fasiddique
body: Has this been updated based on the discussion?
created_at: 2025-05-06T14:13:16Z
author_association: COLLABORATOR
diff_hunk: @@ -531,6 +533,8 @@ pimPerfEnergyTables::bitsimdPerfTable = {
       { PimCmdEnum::REDMIN_RANGE, {   32,    8,   51 } },
       { PimCmdEnum::REDMAX,       {   32,    8,   51 } },
       { PimCmdEnum::REDMAX_RANGE, {   32,    8,   51 } },
+      { PimCmdEnum::AES_SBOX,     {   2450, 2112, 3625 } },
+      { PimCmdEnum::AES_INVERSE_SBOX,     {   2420, 2086, 3610 } },
path: libpimeval/src/pimPerfEnergyTables.cpp
user: login: fasiddique
body: Has this been updated based on the discussion?
created_at: 2025-05-06T14:13:34Z
author_association: COLLABORATOR
diff_hunk: @@ -80,6 +80,11 @@ pimPerfEnergyBankLevel::getPerfEnergyForFunc1(PimCmdEnum cmdType, const pimObjIn
       totalOp = obj.getNumElements();
       break;
     }
+    case PimCmdEnum::AES_SBOX:
+    case PimCmdEnum::AES_INVERSE_SBOX:
+    {
+      numberOfOperationPerElement = 1;  // Assuming each 8-bit element allocates one ALU word 
path: libpimeval/src/pimPerfEnergyBankLevel.cpp
user: login: fasiddique
body: ```suggestion
      // TODO: Use object to decide the value of `numberOfOperationPerElement`
      numberOfOperationPerElement = 1;  // Assuming each 8-bit element allocates one ALU word 
```
created_at: 2025-05-06T14:15:20Z
author_association: COLLABORATOR
diff_hunk: @@ -266,6 +271,10 @@ class pimCmdFunc1 : public pimCmd
         }
         break;
     }
+    case PimCmdEnum::AES_SBOX:
+    case PimCmdEnum::AES_INVERSE_SBOX:
+      result = m_lut[operand]; 
path: libpimeval/src/pimCmd.h
user: login: mhgholamrezaei
body: Since the bit-serial architecture has different performance models for the forward and inverse sbox functions, it is essential to have two separate commands for them. 
created_at: 2025-05-07T03:07:27Z
author_association: COLLABORATOR
diff_hunk: @@ -211,6 +211,8 @@ pimPerfEnergyTables::bitsimdPerfTable = {
       { PimCmdEnum::REDMIN_RANGE, {   32,    8,   42 } },
       { PimCmdEnum::REDMAX,       {   32,    8,   42 } },
       { PimCmdEnum::REDMAX_RANGE, {   32,    8,   42 } },
+      { PimCmdEnum::AES_SBOX,     {   2450, 2112, 3625 } },
+      { PimCmdEnum::AES_INVERSE_SBOX,     {   2420, 2086, 3610 } },
path: libpimeval/src/pimPerfEnergyTables.cpp
user: login: mhgholamrezaei
body: Yes. The stats are based on the `inv_and_xnor_mux` architecture. 
created_at: 2025-05-07T03:25:37Z
author_association: COLLABORATOR
diff_hunk: @@ -531,6 +533,8 @@ pimPerfEnergyTables::bitsimdPerfTable = {
       { PimCmdEnum::REDMIN_RANGE, {   32,    8,   51 } },
       { PimCmdEnum::REDMAX,       {   32,    8,   51 } },
       { PimCmdEnum::REDMAX_RANGE, {   32,    8,   51 } },
+      { PimCmdEnum::AES_SBOX,     {   2450, 2112, 3625 } },
+      { PimCmdEnum::AES_INVERSE_SBOX,     {   2420, 2086, 3610 } },
path: libpimeval/src/pimPerfEnergyTables.cpp
user: login: mhgholamrezaei
body: Yes.
created_at: 2025-05-07T03:25:44Z
author_association: COLLABORATOR
diff_hunk: @@ -80,6 +80,11 @@ pimPerfEnergyBankLevel::getPerfEnergyForFunc1(PimCmdEnum cmdType, const pimObjIn
       totalOp = obj.getNumElements();
       break;
     }
+    case PimCmdEnum::AES_SBOX:
+    case PimCmdEnum::AES_INVERSE_SBOX:
+    {
+      numberOfOperationPerElement = 1;  // Assuming each 8-bit element allocates one ALU word 
path: libpimeval/src/pimPerfEnergyBankLevel.cpp
user: login: mhgholamrezaei
body: @fasiddique, could you clarify what object you're referring to in the comment "TODO: Use object to decide the value of numberOfOperationPerElement"?
created_at: 2025-05-07T03:33:31Z
author_association: COLLABORATOR
diff_hunk: @@ -266,6 +271,10 @@ class pimCmdFunc1 : public pimCmd
         }
         break;
     }
+    case PimCmdEnum::AES_SBOX:
+    case PimCmdEnum::AES_INVERSE_SBOX:
+      result = m_lut[operand]; 
path: libpimeval/src/pimCmd.h
user: login: deyuan
body: Makes sense. Thanks!
created_at: 2025-05-07T07:36:43Z
author_association: COLLABORATOR
diff_hunk: @@ -198,6 +198,11 @@ PimStatus pimShiftElementsLeft(PimObjId src);
 PimStatus pimShiftBitsRight(PimObjId src, PimObjId dest, unsigned shiftAmount);
 PimStatus pimShiftBitsLeft(PimObjId src, PimObjId dest, unsigned shiftAmount);
 
+// AES sbox and inverse-box APIs
+// Note: AES S-box and inverse S-box are treated separately because their bit-serial performance models differ.
+// However, it is the user's responsibility to provide the appropriate LUT to ensure correct functionality.
path: libpimeval/src/libpimeval.h
user: login: deyuan
body: It would be great to mention that pimAesInverseSbox requires an inverse LUT as the input parameter
created_at: 2025-05-07T07:39:24Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,116 @@
+// Test: Test reduction min
+// Copyright (c) 2024 University of Virginia
+// This file is licensed under the MIT License.
+// See the LICENSE file in the root of this repository for more details.
+
+#include "libpimeval.h"
+#include <iostream>
+#include <vector>
+#include <cassert>
+#include <cstdlib>
+#include <cstdint>
+#include <limits> // Include this for UINT_MAX
+                  //
+// Forward S-box table for SubBytes step in AES algorithm.
+const uint8_t sbox[256] = {
+  0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
+    0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
+    0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
+    0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
+    0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
+    0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
+    0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
+    0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
+    0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
+    0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
+    0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
+    0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
+    0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
+    0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
+    0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
+    0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
+    0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
+    0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
+    0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
+    0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
+    0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
+    0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
+    0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
+    0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
+    0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
+    0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
+    0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
+    0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
+    0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
+    0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
+    0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
+    0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
+};
+
+bool testAesSbox(PimDeviceEnum deviceType)
+{
+  std::vector<uint8_t> lut_vec(std::begin(sbox), std::end(sbox));
+
+  unsigned numRanks = 2;
+  unsigned numBankPerRank = 2;
+  unsigned numSubarrayPerBank = 8;
+  unsigned numRows = 1024;
+  unsigned numCols = 8192;
+
+  uint64_t numElements = 256;
+  std::vector<uint8_t> src(numElements);
+  std::vector<uint8_t> dest(numElements);
+
+  for (uint64_t i = 0; i < numElements; ++i) {
+    src[i] = i;
+  }
+
+  PimStatus status = pimCreateDevice(deviceType, numRanks, numBankPerRank, numSubarrayPerBank, numRows, numCols);
+  assert(status == PIM_OK);
+  
+  bool ok = true;
+  PimObjId srcObj = pimAlloc(PIM_ALLOC_AUTO, numElements, PIM_UINT8);
+  assert(srcObj != -1);
+
+  PimObjId destObj = pimAllocAssociated(srcObj, PIM_UINT8);
+  assert(destObj != -1);
+
+  status = pimCopyHostToDevice((void*)src.data(), srcObj);
+  assert(status == PIM_OK);
+
+  status = pimAesSbox(srcObj, destObj, lut_vec);
path: tests/test-aes-sbox/test-aes-sbox.cpp
user: login: deyuan
body: It would be great to cover pimAesInverseSbox as well
created_at: 2025-05-07T07:41:06Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,116 @@
+// Test: Test reduction min
+// Copyright (c) 2024 University of Virginia
+// This file is licensed under the MIT License.
+// See the LICENSE file in the root of this repository for more details.
+
+#include "libpimeval.h"
+#include <iostream>
+#include <vector>
+#include <cassert>
+#include <cstdlib>
+#include <cstdint>
+#include <limits> // Include this for UINT_MAX
+                  //
+// Forward S-box table for SubBytes step in AES algorithm.
+const uint8_t sbox[256] = {
+  0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
+    0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
+    0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
+    0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
+    0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
+    0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
+    0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
+    0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
+    0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
+    0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
+    0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
+    0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
+    0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
+    0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
+    0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
+    0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
+    0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
+    0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
+    0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
+    0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
+    0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
+    0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
+    0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
+    0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
+    0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
+    0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
+    0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
+    0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
+    0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
+    0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
+    0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
+    0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
+};
+
+bool testAesSbox(PimDeviceEnum deviceType)
+{
+  std::vector<uint8_t> lut_vec(std::begin(sbox), std::end(sbox));
+
+  unsigned numRanks = 2;
+  unsigned numBankPerRank = 2;
+  unsigned numSubarrayPerBank = 8;
+  unsigned numRows = 1024;
+  unsigned numCols = 8192;
+
+  uint64_t numElements = 256;
+  std::vector<uint8_t> src(numElements);
+  std::vector<uint8_t> dest(numElements);
+
+  for (uint64_t i = 0; i < numElements; ++i) {
+    src[i] = i;
+  }
+
+  PimStatus status = pimCreateDevice(deviceType, numRanks, numBankPerRank, numSubarrayPerBank, numRows, numCols);
+  assert(status == PIM_OK);
+  
+  bool ok = true;
+  PimObjId srcObj = pimAlloc(PIM_ALLOC_AUTO, numElements, PIM_UINT8);
+  assert(srcObj != -1);
+
+  PimObjId destObj = pimAllocAssociated(srcObj, PIM_UINT8);
+  assert(destObj != -1);
+
+  status = pimCopyHostToDevice((void*)src.data(), srcObj);
+  assert(status == PIM_OK);
+
+  status = pimAesSbox(srcObj, destObj, lut_vec);
path: tests/test-aes-sbox/test-aes-sbox.cpp
user: login: mhgholamrezaei
body: Thanks. It is done.
created_at: 2025-05-07T13:42:25Z
author_association: COLLABORATOR
diff_hunk: @@ -198,6 +198,11 @@ PimStatus pimShiftElementsLeft(PimObjId src);
 PimStatus pimShiftBitsRight(PimObjId src, PimObjId dest, unsigned shiftAmount);
 PimStatus pimShiftBitsLeft(PimObjId src, PimObjId dest, unsigned shiftAmount);
 
+// AES sbox and inverse-box APIs
+// Note: AES S-box and inverse S-box are treated separately because their bit-serial performance models differ.
+// However, it is the user's responsibility to provide the appropriate LUT to ensure correct functionality.
path: libpimeval/src/libpimeval.h
user: login: mhgholamrezaei
body: Thanks for your comment. It is addressed in the new commit.
created_at: 2025-05-07T13:42:50Z
author_association: COLLABORATOR

PR comments:
user: login: fasiddique
created_at: 2025-05-02T13:56:18Z
body: Thanks @mhgholamrezaei! I have some questions:
1. Why the API was not modeled for Fulcrum?
2. For Bank-level, I can see that write latency is being added to the runtime. Could you please elaborate your reasoning behind the performance and energy modeling for the bank-level PIM.
author_association: COLLABORATOR
user: login: mhgholamrezaei
created_at: 2025-05-02T23:46:51Z
body: @fasiddique, thanks for the review. 

1. The lookup table (LUT) is not implemented for the Fulcrum architecture due to the high overhead of maintaining a LUT per core. Per Kevin’s suggestion, the LUT is only supported in the Bank-level architecture, and the S-box operation is offloaded to the host during Fulcrum execution.
2. The performance and energy model for the Bank-level architecture is based on the Func1 operation, as the S-box involves a single input and a single output. Additionally, it is assumed that the LUT access latency is absorbed within the pipeline.

author_association: COLLABORATOR
user: login: fasiddique
created_at: 2025-05-03T01:04:19Z
body: > @fasiddique, thanks for the review.
> 
> 1. The lookup table (LUT) is not implemented for the Fulcrum architecture due to the high overhead of maintaining a LUT per core. Per Kevin’s suggestion, the LUT is only supported in the Bank-level architecture, and the S-box operation is offloaded to the host during Fulcrum execution.

Understood! But even if Fulcrum does not support this, we need to add a switch case for this in the Fulcrum, may be in the func1 command. So that, if the user run this benchmark for Fulcrum, they will not get some segfault or any other error.

> 2. The performance and energy model for the Bank-level architecture is based on the Func1 operation, as the S-box involves a single input and a single output. Additionally, it is assumed that the LUT access latency is absorbed within the pipeline.

Thank you! I am thinking in this case can we just use the func1 and add switch cases to it for the new API? 
author_association: COLLABORATOR
user: login: mhgholamrezaei
created_at: 2025-05-03T01:33:15Z
body: 
@fasiddique, thanks for your suggestions.

1. For the Fulcrum performance model, we can display an error message and halt execution. Should this check be implemented within the PIM command itself, or would it be better to create a new performance model class and handle the error there? Do you know of any existing command where the performance model is not implemented for a specific architecture, as a reference?

2. The performance behavior of this command is similar to Func1, but it also takes an additional parameter lut. Integrating this into Func1 would reduce code readability and introduce added complexity—especially within the sanityCheck function. I would prefer to implement a separate PIM command for the S-box. However, if you believe it’s more appropriate to merge it into Func1, I’m open to that as well.





author_association: COLLABORATOR
user: login: fasiddique
created_at: 2025-05-04T16:05:26Z
body: > @fasiddique, thanks for your suggestions.
> 
> 1. For the Fulcrum performance model, we can display an error message and halt execution. Should this check be implemented within the PIM command itself, or would it be better to create a new performance model class and handle the error there? Do you know of any existing command where the performance model is not implemented for a specific architecture, as a reference?
> 2. The performance behavior of this command is similar to Func1, but it also takes an additional parameter lut. Integrating this into Func1 would reduce code readability and introduce added complexity—especially within the sanityCheck function. I would prefer to implement a separate PIM command for the S-box. However, if you believe it’s more appropriate to merge it into Func1, I’m open to that as well.

Some suggestions:
I went over the code and saw that this API can be handled by Func1 or Func2 performance/energy model. There's no need to create a separate performance/energy modeling function for this for different PIM architecture. The reason being, this instruction does not require any special handling. We needed to separate reduction, broadcast and rotation because they needed some special handling like inter-bank communication, host execution and such. In the command class, however, you can have separate class for this. 
If an architecture does not support an API, you can show something like following under that command's switch case.
`std::cout << "PIM-Warning: Unsupported for Fulcrum: " << pimCmd::getName(cmdType, "") << std::endl;`
I am also interested to know, if the user want to run AES benchmark for Fulcrum, how will they go about it? Do we have separate implementation for Fulcrum vs other PIM architectures?
author_association: COLLABORATOR
user: login: mhgholamrezaei
created_at: 2025-05-04T18:18:53Z
body: Thank you!

`pimCmdFunc1` is exteded in the new version for implementation of the AES functions. 

For the Fulcrum architecture, AES execution is handled by the host. The `aesSubBytes` function in aes.cpp will check the PIM architecture, and if it is Fulcrum, the S-box computation will be performed on the host.
author_association: COLLABORATOR
user: login: fasiddique
created_at: 2025-05-06T14:16:54Z
body: @mhgholamrezaei I think this is ready to be merged specially after verifying the bitserial table is correct. Thank you!

PS. Please rebase with main branch
author_association: COLLABORATOR
user: login: mhgholamrezaei
created_at: 2025-05-07T13:42:08Z
body: New changes:
- Updated comments in libpimeval.
- Added test for AES inverse sbox function.
- Added comment to [libpimeval/src/pimPerfEnergyBankLevel.cpp](https://github.com/UVA-LavaLab/PIMeval-PIMbench/pull/277/files/e6025e173bb1223f6da673ec314451d13f9e2882#diff-138997d3950a8937cfa16e2a9426819d63d6a9373c01632b0b75a57cb74426f6) to justify why the `numberOfOperationPerElement `is 0.
author_association: COLLABORATOR