//////////////////////////////////////////////////////////////////////////////////////////////////
// Assigment : write a CUDA code corresponding to the
// following sequential C code
//
// #include <stdio.h>
// #define N 100
// int main()
// {
//   int A[N];
//
//   for (int i=0 ; i<N ; i++)
//     A[i] = (i * i);
//
//   return 0;
// }
//////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////
// Author: David Goz
// mail  : david.goz@inaf.it
// date  : 06.07.2024
// code tested using nvhpc
//
// - Compile the code:
//   $ nvc++ classwork_2.cu -o classwork_2
// - Run the code:
//   $ ./classwork_2
// - Check the result:
//   $ ./classwork_2 | tail -n 100 | sort -nk 5

//////////////////////////////////////////////////////////////////////////////////////////////////

#include <stdlib.h>
#include <stdio.h>
#include <cuda.h>
#include <assert.h>

#define N        100
#define NThreads 1024

__global__ void GPUkernel(      int *A,
			  const int size)
{
  const int myID = threadIdx.x + (blockIdx.x * blockDim.x);
  
  if (myID < size)
    A[myID] = (myID * myID);

  return;
}

int main()
{
  /* host array */
  int *h_A = (int *)malloc(N * sizeof(*h_A));
  assert(h_A != NULL);

  /* device array */
  int *d_A = NULL;
  cudaMalloc((void **)&d_A, (N * sizeof(*d_A)));
  assert(d_A != NULL);
  
  // kernel lunch
  GPUkernel<<<1, NThreads>>>(d_A, N);
  
  // device synchronization
  cudaDeviceSynchronize();

  // fetch device memory
  cudaMemcpy(h_A, d_A, (sizeof(*h_A) * N), cudaMemcpyDeviceToHost);

  // free GPU memory
  cudaFree(d_A);
  
  // check the result
  printf("\n");
  for (size_t i=0 ; i<N ; i++)
    printf("\t A[%d] = %d", i, h_A[i]);
  printf("\n\n");

  // free host memory
  free(h_A);
  
  return 0;
}
