/**
 * Get CUDA Cores per Multiprocessor and total CUDA Cores
 * This program demonstrates how to obtain the number of CUDA cores per streaming multiprocessor (SM/MP)
 * and calculate the total CUDA cores for each CUDA-capable device.
 */

#include <stdio.h>
#include <cuda_runtime.h>

/**
 * Convert SM version to number of CUDA cores per multiprocessor
 * This function is based on NVIDIA's official conversion table from CUDA Samples
 * 
 * @param major Compute capability major version
 * @param minor Compute capability minor version
 * @return Number of CUDA cores per streaming multiprocessor
 */
int _ConvertSMVer2Cores(int major, int minor) {
    // Table mapping SM version (encoded as 0xMm) to cores per MP
    typedef struct {
        int SM;     // 0xMm (hex representation of major.minor, e.g., 0x75 for 7.5)
        int Cores;  // Number of CUDA cores per MP for this architecture
    } sSMtoCores;
    
    // NVIDIA GPU architecture versions and their corresponding cores per MP
    sSMtoCores nGpuArchCoresPerSM[] = {
        { 0x10,  8 },  // Tesla Generation (SM 1.0) - G80, G92
        { 0x11,  8 },  // Tesla Generation (SM 1.1) - G86, G84
        { 0x12,  8 },  // Tesla Generation (SM 1.2) - G98, G96
        { 0x13,  8 },  // Tesla Generation (SM 1.3) - GT200, GT200b
        
        { 0x20, 32 },  // Fermi Generation (SM 2.0) - GF100, GF110
        { 0x21, 48 },  // Fermi Generation (SM 2.1) - GF104, GF114, GF116
        
        { 0x30, 192 }, // Kepler Generation (SM 3.0) - GK104, GK106
        { 0x32, 192 }, // Kepler Generation (SM 3.2) - GK20A (Tegra)
        { 0x35, 192 }, // Kepler Generation (SM 3.5) - GK110, GK208
        { 0x37, 192 }, // Kepler Generation (SM 3.7) - GK210
        
        { 0x50, 128 }, // Maxwell Generation (SM 5.0) - GM107, GM108
        { 0x52, 128 }, // Maxwell Generation (SM 5.2) - GM200, GM204, GM206
        { 0x53, 128 }, // Maxwell Generation (SM 5.3) - GM20B (Tegra)
        
        { 0x60, 64 },  // Pascal Generation (SM 6.0) - GP100
        { 0x61, 128 }, // Pascal Generation (SM 6.1) - GP104, GP106, GP107, GP108
        { 0x62, 128 }, // Pascal Generation (SM 6.2) - GP10B (Tegra)
        
        { 0x70, 64 },  // Volta Generation (SM 7.0) - GV100
        { 0x72, 64 },  // Volta Generation (SM 7.2) - GV10B (Tegra)
        { 0x75, 64 },  // Turing Generation (SM 7.5) - TU102, TU104, TU106, TU116, TU117
        
        { 0x80, 128 }, // Ampere Generation (SM 8.0) - GA100 (A100)
        { 0x86, 128 }, // Ampere Generation (SM 8.6) - GA102, GA104, GA106, GA107 (RTX 30 series)
        { 0x87, 128 }, // Ampere Generation (SM 8.7) - GA10B (Orin, Jetson AGX Orin)
        { 0x89, 128 }, // Ada Lovelace Generation (SM 8.9) - AD102, AD103, AD104, AD106, AD107 (RTX 40 series)
        
        { 0x90, 128 }, // Hopper Generation (SM 9.0) - GH100 (H100)
        
        { -1, -1 }     // Terminator - marks the end of the table
    };
    
    int index = 0;
    // Encode major.minor as a single hex value (e.g., 8.9 -> 0x89)
    int sm = (major << 4) + minor;
    
    // Search the table for a matching SM version
    while (nGpuArchCoresPerSM[index].SM != -1) {
        if (nGpuArchCoresPerSM[index].SM == sm) {
            return nGpuArchCoresPerSM[index].Cores;
        }
        index++;
    }
    
    // If no exact match found, estimate based on major version
    printf("Warning: Unknown SM version %d.%d, estimating cores per MP based on major version\n", 
           major, minor);
    
    // Estimate cores per MP based on architecture generation
    if (major < 2) return 8;      // Tesla architecture
    if (major == 2) return (minor == 0) ? 32 : 48;  // Fermi
    if (major == 3) return 192;    // Kepler
    if (major == 5) return 128;    // Maxwell
    if (major == 6) return (minor == 0) ? 64 : 128;  // Pascal
    if (major == 7) return 64;     // Volta, Turing
    if (major >= 8) return 128;    // Ampere, Ada Lovelace, Hopper and future architectures
    
    return -1;  // Unknown architecture
}

int main() {
    int deviceCount;
    cudaError_t error = cudaGetDeviceCount(&deviceCount);
    
    if (error != cudaSuccess) {
        printf("Error: Failed to get device count: %s\n", cudaGetErrorString(error));
        return 1;
    }
    
    if (deviceCount == 0) {
        printf("No CUDA-capable devices found\n");
        return 0;
    }
    
    printf("Found %d CUDA-capable device(s)\n\n", deviceCount);
    
    // Iterate through all available devices
    for (int i = 0; i < deviceCount; i++) {
        cudaDeviceProp prop;
        cudaGetDeviceProperties(&prop, i);
        
        // Get cores per MP for this device based on its compute capability
        int coresPerMP = _ConvertSMVer2Cores(prop.major, prop.minor);
        
        // Calculate total CUDA cores: cores_per_MP * number_of_MPs
        int totalCores = coresPerMP * prop.multiProcessorCount;
        
        // Device identification
        printf("Device %d: %s\n", i, prop.name);
        printf("  PCI Bus ID: %04x:%02x:%02x.0\n", 
               prop.pciDomainID, prop.pciBusID, prop.pciDeviceID);
        
        // Compute capability information
        printf("  Compute Capability: %d.%d\n", prop.major, prop.minor);
        
        // Multiprocessor information
        printf("  Multiprocessors (MP/SM): %d\n", prop.multiProcessorCount);
        printf("  CUDA Cores per MP: %d\n", coresPerMP);
        printf("  Total CUDA Cores: %d\n", totalCores);
        
        // Clock information
        printf("  GPU Clock Rate: %.2f GHz\n", prop.clockRate * 1e-6);
        printf("  Memory Clock Rate: %.2f GHz\n", prop.memoryClockRate * 1e-6);
        
        // Memory information
        printf("  Total Global Memory: %.2f GB\n", 
               prop.totalGlobalMem / (1024.0 * 1024.0 * 1024.0));
        printf("  L2 Cache Size: %d KB\n", prop.l2CacheSize / 1024);
        // total amount of shared memory per block
        printf("  Shared Memory per Block: %lu bytes\n", prop.sharedMemPerBlock);
        printf("  Total constant Mem:  %lu bytes\n", prop.totalConstMem);
        // total number of registers available per block
        printf("  Registers per Block: %d\n", prop.regsPerBlock);
        
        // Warp and thread information
        printf("  Warp Size: %d threads\n", prop.warpSize);
        printf("  Max Threads per Block: %d\n", prop.maxThreadsPerBlock);
        printf("  Max Threads per Multiprocessor: %d\n", 
               prop.maxThreadsPerMultiProcessor);
        printf("  Max Thread Dimensions: (%d, %d, %d)\n", 
               prop.maxThreadsDim[0], prop.maxThreadsDim[1], prop.maxThreadsDim[2]);
        printf("  Max Grid Dimensions: (%d, %d, %d)\n", 
               prop.maxGridSize[0], prop.maxGridSize[1], prop.maxGridSize[2]);
        
        // Feature support
        printf("  Unified Addressing: %s\n", prop.unifiedAddressing ? "Yes" : "No");
        printf("  Managed Memory: %s\n", prop.managedMemory ? "Yes" : "No");
        printf("  Concurrent Kernels: %s\n", prop.concurrentKernels ? "Yes" : "No");
        
        printf("  ------------------------------\n");
    }
    
    return 0;
}

// Device 0: NVIDIA GeForce RTX 4090
//   PCI Bus ID: 0000:4f:00.0
//   Compute Capability: 8.9
//   Multiprocessors (MP/SM): 128
//   CUDA Cores per MP: 128
//   Total CUDA Cores: 16384
//   GPU Clock Rate: 2.52 GHz
//   Memory Clock Rate: 10.50 GHz
//   Total Global Memory: 23.64 GB
//   L2 Cache Size: 73728 KB
//   Total global mem:  25386352640
//   Total constant Mem:  65536
//   Warp Size: 32 threads
//   Max Threads per Block: 1024
//   Max Threads per Multiprocessor: 1536
//   Max Thread Dimensions: (1024, 1024, 64)
//   Max Grid Dimensions: (2147483647, 65535, 65535)
//   Unified Addressing: Yes
//   Managed Memory: Yes
//   Concurrent Kernels: Yes
