
# Direct CMake to use icpx rather than the default C++ compiler/linker on Linux
# and icx-cl on Windows
if(UNIX)
    set(CMAKE_CXX_COMPILER icpx)
else() # Windows
    include (CMakeForceCompiler)
    CMAKE_FORCE_CXX_COMPILER (icx-cl IntelDPCPP)
    include (Platform/Windows-Clang)
endif()

cmake_minimum_required (VERSION 3.7.2)

project(use_library CXX)

set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})

###############################################################################
### Customize these build variables
###############################################################################
set(SOURCE_FILES src/use_library.cpp)
set(TARGET_NAME use_library)
set(LIB_FILENAME lib_rtl)

# Library source files
set(RTL_C_MODEL src/${LIB_FILENAME}_model.cpp)
set(RTL_SPEC src/${LIB_FILENAME}_spec.xml)
set(RTL_V src/${LIB_FILENAME}.v)
if (WIN32)
    set(RTL_SOURCE_OBJECT ${LIB_FILENAME}.obj)
    set(LIBRARY_ARCHIVE ${LIB_FILENAME}.lib)
else()
    set(RTL_SOURCE_OBJECT ${LIB_FILENAME}.o)
    set(LIBRARY_ARCHIVE ${LIB_FILENAME}.a)
endif()

# Use cmake -DFPGA_DEVICE=<board-support-package>:<board-variant> to choose a
# different device. Here are a few device examples (this list is not
# exhaustive):
#   intel_s10sx_pac:pac_s10
#   intel_s10sx_pac:pac_s10_usm
#   intel_a10gx_pac:pac_a10
# Note that depending on your installation, you may need to specify the full 
# path to the board support package (BSP), this usually is in your install 
# folder.
#
# You can also specify a device family (E.g. "Arria10" or "Stratix10") or a
# specific part number (E.g. "10AS066N3F40E2SG") to generate a standalone IP.
if(NOT DEFINED FPGA_DEVICE)
    set(FPGA_DEVICE "Agilex7")
    message(STATUS "FPGA_DEVICE was not specified.\
                    \nConfiguring the design to the default FPGA family target: ${FPGA_DEVICE}\
                    \nPlease refer to the README for information on target selection.")
    set(DEVICE_FLAG "Agilex7")
else()
    string(TOLOWER ${FPGA_DEVICE} FPGA_DEVICE_NAME)
    if(FPGA_DEVICE_NAME MATCHES ".*a10.*" OR FPGA_DEVICE_NAME MATCHES ".*arria10.*")
      set(DEVICE_FLAG "A10")
    elseif(FPGA_DEVICE_NAME MATCHES ".*s10.*" OR FPGA_DEVICE_NAME MATCHES ".*stratix10.*")
      set(DEVICE_FLAG "S10")
    elseif(FPGA_DEVICE_NAME MATCHES ".*agilex7.*")
      set(DEVICE_FLAG "Agilex7")
    elseif(FPGA_DEVICE_NAME MATCHES ".*agilex5.*")
      set(DEVICE_FLAG "Agilex5")
    elseif(FPGA_DEVICE_NAME MATCHES ".*cyclonev.*")
      set(DEVICE_FLAG "CycloneV")
    endif()
    message(STATUS "Configuring the design with the following target: ${FPGA_DEVICE}")
endif()

if(NOT DEFINED DEVICE_FLAG)
    message(FATAL_ERROR "An unrecognized or custom board was passed, but DEVICE_FLAG was not specified. \
                         Please make sure you have set -DDEVICE_FLAG=A10, -DDEVICE_FLAG=S10, -DDEVICE_FLAG=CycloneV, \
                         -DDEVICE_FLAG=Agilex5, or -DDEVICE_FLAG=Agilex7.")
endif()

# Use cmake -DUSER_FPGA_FLAGS=<flags> to set extra flags for FPGA backend
# compilation. 
set(USER_FPGA_FLAGS ${USER_FPGA_FLAGS};-Xsoptimize=latency)

# Use cmake -DUSER_FLAGS=<flags> to set extra flags for general compilation.
# use -Wno-return-type-c-linkage to disable warnings caused by using ac_int types in the C++ model
set(USER_FLAGS ${USER_FLAGS};-Wno-return-type-c-linkage)

# Use cmake -DUSER_INCLUDE_PATHS=<paths> to set extra paths for general
# compilation.
set(USER_INCLUDE_PATHS ../../../include;${USER_INCLUDE_PATHS})

###############################################################################
### no changes after here
###############################################################################

# Set the names of the makefile targets to be generated by cmake
set(EMULATOR_TARGET fpga_emu)
set(SIMULATOR_TARGET fpga_sim)
set(REPORT_TARGET report)
set(FPGA_TARGET fpga)

# Set the names of the generated files per makefile target
set(EMULATOR_OUTPUT_NAME ${TARGET_NAME}.${EMULATOR_TARGET})
set(SIMULATOR_OUTPUT_NAME ${TARGET_NAME}.${SIMULATOR_TARGET})
set(REPORT_OUTPUT_NAME ${TARGET_NAME}.${REPORT_TARGET})
set(FPGA_OUTPUT_NAME ${TARGET_NAME}.${FPGA_TARGET})

message(STATUS "Additional USER_FPGA_FLAGS=${USER_FPGA_FLAGS}")
message(STATUS "Additional USER_FLAGS=${USER_FLAGS}")

include_directories(${USER_INCLUDE_PATHS})
message(STATUS "Additional USER_INCLUDE_PATHS=${USER_INCLUDE_PATHS}")

link_directories(${USER_LIB_PATHS})
message(STATUS "Additional USER_LIB_PATHS=${USER_LIB_PATHS}")

link_libraries(${USER_LIBS})
message(STATUS "Additional USER_LIBS=${USER_LIBS}")

message(STATUS "SYCL LIBRARY_ARCHIVE=${LIBRARY_ARCHIVE}")

if(WIN32)
    # add qactypes for Windows
    set(QACTYPES "-Qactypes")
    # This is a Windows-specific flag that enables exception handling in host code
    set(WIN_FLAG "/EHsc")
else()
    # add qactypes for Linux
    set(QACTYPES "-qactypes")
endif()

string(TOLOWER "${CMAKE_BUILD_TYPE}" LOWER_BUILD_TYPE)
if(LOWER_BUILD_TYPE MATCHES debug)
# Set debug flags
    if(WIN32)
        set(DEBUG_FLAGS /DEBUG /Od)
    else()
        set(DEBUG_FLAGS -g -O0)
    endif()
else()
    set(DEBUG_FLAGS "")
endif()

if(WIN32)
    set(EXT ".exe")
endif()

set(COMMON_COMPILE_FLAGS -fintelfpga -Wall ${WIN_FLAG} ${QACTYPES} ${USER_FLAGS})
set(COMMON_LINK_FLAGS -fintelfpga ${QACTYPES} ${USER_FLAGS})

# A SYCL ahead-of-time (AoT) compile processes the device code in two stages.
# 1. The "compile" stage compiles the device code to an intermediate
#    representation (SPIR-V).
# 2. The "link" stage invokes the compiler's FPGA backend before linking. For
#    this reason, FPGA backend flags must be passed as link flags in CMake.
set(EMULATOR_COMPILE_FLAGS -DFPGA_EMULATOR ${DEBUG_FLAGS})
set(EMULATOR_LINK_FLAGS )
set(REPORT_COMPILE_FLAGS -DFPGA_HARDWARE)
set(REPORT_LINK_FLAGS -Xshardware -Xstarget=${FPGA_DEVICE} ${USER_FPGA_FLAGS} -fsycl-link=early)
set(SIMULATOR_COMPILE_FLAGS -Xssimulation -DFPGA_SIMULATOR)
set(SIMULATOR_LINK_FLAGS -Xssimulation -Xsghdl -Xstarget=${FPGA_DEVICE} ${USER_FPGA_FLAGS} -reuse-exe=${CMAKE_BINARY_DIR}/${SIMULATOR_OUTPUT_NAME}${EXT})
set(FPGA_COMPILE_FLAGS -DFPGA_HARDWARE)
set(FPGA_LINK_FLAGS -Xshardware -Xstarget=${FPGA_DEVICE} ${USER_FPGA_FLAGS} -reuse-exe=${CMAKE_BINARY_DIR}/${FPGA_OUTPUT_NAME}${EXT} -fsycl-device-code-split=per_kernel)

###############################################################################
### Generate Library
###############################################################################

# The FPGA family in lib_rtl.v must be set according to the target family
if(DEVICE_FLAG MATCHES A10)
    set(FAMILY "Arria 10")
elseif(DEVICE_FLAG MATCHES S10)
    set(FAMILY "Stratix 10")
elseif(DEVICE_FLAG MATCHES CycloneV)
    set(FAMILY "Cyclone V")
else()
    set(FAMILY "Agilex")
endif()

# The RTL file (specified in lib_rtl_spec.xml) must be copied to the CMake working directory for the final stage of FPGA hardware compilation
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${RTL_V} ${RTL_V} @ONLY)

set(FPGA_CROSSGEN_COMMAND fpga_crossgen ${CMAKE_CURRENT_SOURCE_DIR}/${RTL_SPEC} --cpp_model ${CMAKE_CURRENT_SOURCE_DIR}/${RTL_C_MODEL} -o ${RTL_SOURCE_OBJECT} ${USER_FLAGS})

# Create RTL source object
add_custom_target(
    create_rtl_source_object
    COMMAND ${FPGA_CROSSGEN_COMMAND}
)

# Create library archive
# This executes:
# fpga_libtool lib_rtl.o --create lib.a
add_custom_target(create_library_archive DEPENDS ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/${LIBRARY_ARCHIVE})
set(FPGA_LIBTOOL_COMMAND fpga_libtool ${RTL_SOURCE_OBJECT} --create ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/${LIBRARY_ARCHIVE})
add_custom_command(OUTPUT ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/${LIBRARY_ARCHIVE}
                   COMMAND ${FPGA_LIBTOOL_COMMAND}
                   DEPENDS create_rtl_source_object)

# Tell CMake to recognize our custom library
add_library(library_archive STATIC IMPORTED GLOBAL)
add_dependencies(library_archive create_library_archive)
set_target_properties(library_archive PROPERTIES IMPORTED_LOCATION ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/${LIBRARY_ARCHIVE})

###############################################################################
### FPGA Emulator
###############################################################################
add_executable(${EMULATOR_TARGET} ${SOURCE_FILES})
target_compile_options(${EMULATOR_TARGET} PRIVATE ${COMMON_COMPILE_FLAGS})
target_compile_options(${EMULATOR_TARGET} PRIVATE ${EMULATOR_COMPILE_FLAGS})
target_link_libraries(${EMULATOR_TARGET} ${COMMON_LINK_FLAGS})
target_link_libraries(${EMULATOR_TARGET} ${EMULATOR_LINK_FLAGS})
target_link_libraries(${EMULATOR_TARGET} library_archive)
set_target_properties(${EMULATOR_TARGET} PROPERTIES OUTPUT_NAME ${EMULATOR_OUTPUT_NAME})

###############################################################################
### FPGA Simulator
###############################################################################
add_executable(${SIMULATOR_TARGET} EXCLUDE_FROM_ALL ${SOURCE_FILES})
target_compile_options(${SIMULATOR_TARGET} PRIVATE ${COMMON_COMPILE_FLAGS})
target_compile_options(${SIMULATOR_TARGET} PRIVATE ${SIMULATOR_COMPILE_FLAGS})
target_link_libraries(${SIMULATOR_TARGET} ${COMMON_LINK_FLAGS})
target_link_libraries(${SIMULATOR_TARGET} ${SIMULATOR_LINK_FLAGS})
target_link_libraries(${SIMULATOR_TARGET} library_archive)
set_target_properties(${SIMULATOR_TARGET} PROPERTIES OUTPUT_NAME ${SIMULATOR_OUTPUT_NAME})

###############################################################################
### Generate Report
###############################################################################
add_executable(${REPORT_TARGET} EXCLUDE_FROM_ALL ${SOURCE_FILES})
target_compile_options(${REPORT_TARGET} PRIVATE ${COMMON_COMPILE_FLAGS})
target_compile_options(${REPORT_TARGET} PRIVATE ${REPORT_COMPILE_FLAGS})

# The report target does not need the QACTYPES flag at link stage
set(MODIFIED_COMMON_LINK_FLAGS_REPORT ${COMMON_LINK_FLAGS})
if(UNIX)
    list(REMOVE_ITEM MODIFIED_COMMON_LINK_FLAGS_REPORT ${QACTYPES})
endif()

target_link_libraries(${REPORT_TARGET} ${MODIFIED_COMMON_LINK_FLAGS_REPORT})
target_link_libraries(${REPORT_TARGET} ${REPORT_LINK_FLAGS})
target_link_libraries(${REPORT_TARGET} library_archive)
set_target_properties(${REPORT_TARGET} PROPERTIES OUTPUT_NAME ${REPORT_OUTPUT_NAME})

###############################################################################
### FPGA Hardware
###############################################################################
add_executable(${FPGA_TARGET} EXCLUDE_FROM_ALL ${SOURCE_FILES})
target_compile_options(${FPGA_TARGET} PRIVATE ${COMMON_COMPILE_FLAGS})
target_compile_options(${FPGA_TARGET} PRIVATE ${FPGA_COMPILE_FLAGS})
target_link_libraries(${FPGA_TARGET} ${COMMON_LINK_FLAGS})
target_link_libraries(${FPGA_TARGET} ${FPGA_LINK_FLAGS})
target_link_libraries(${FPGA_TARGET} library_archive)
set_target_properties(${FPGA_TARGET} PROPERTIES OUTPUT_NAME ${FPGA_OUTPUT_NAME})

###############################################################################
### This part only manipulates cmake variables to print the commands cmake is expected to run to the user
###############################################################################

# set the correct object file extension depending on the target platform
if(WIN32)
    set(OBJ_EXTENSION "obj")
else()
    set(OBJ_EXTENSION "o")
endif()

# Set the source file names in a string
set(SOURCE_FILE_NAME "${SOURCE_FILES}")

function(getCompileCommands common_compile_flags special_compile_flags common_link_flags special_link_flags target output_name)

    set(file_names ${SOURCE_FILE_NAME})
    set(COMPILE_COMMAND )
    set(LINK_COMMAND )

    foreach(source ${file_names})
        # Get the relative path to the source and object files
        file(RELATIVE_PATH CURRENT_SOURCE_FILE ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_LIST_DIR}/${source})
        file(RELATIVE_PATH OBJ_FILE ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${target}.dir/${source}.${OBJ_EXTENSION})
        
        # Creating a string that contains the compile command
        # Start by the compiler invocation
        set(COMPILE_COMMAND "${COMPILE_COMMAND}${CMAKE_CXX_COMPILER}")

        # Add all the potential includes
        foreach(INCLUDE ${USER_INCLUDE_PATHS})
            if(NOT IS_ABSOLUTE ${INCLUDE})
                file(RELATIVE_PATH INCLUDE ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_LIST_DIR}/${INCLUDE})
            endif()
            set(COMPILE_COMMAND "${COMPILE_COMMAND} -I${INCLUDE}")
        endforeach()

        # Add all the common compile flags
        foreach(FLAG ${common_compile_flags})
            set(COMPILE_COMMAND "${COMPILE_COMMAND} ${FLAG}")
        endforeach()

        # Add all the specific compile flags
        foreach(FLAG ${special_compile_flags})
            set(COMPILE_COMMAND "${COMPILE_COMMAND} ${FLAG}")
        endforeach()

        # Get the location of the object file
        file(RELATIVE_PATH OBJ_FILE ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${target}.dir/${source}.${OBJ_EXTENSION})

        # Add the source file and the output file
        set(COMPILE_COMMAND "${COMPILE_COMMAND} -c ${CURRENT_SOURCE_FILE} -o ${OBJ_FILE}\n")
    endforeach()

    set(COMPILE_COMMAND "${COMPILE_COMMAND}" PARENT_SCOPE)

    # Creating a string that contains the link command
    # Start by the compiler invocation
    set(LINK_COMMAND "${LINK_COMMAND}${CMAKE_CXX_COMPILER}")

    # Add all the common link flags
    foreach(FLAG ${common_link_flags})
        set(LINK_COMMAND "${LINK_COMMAND} ${FLAG}")
    endforeach()

    # Add all the specific link flags
    foreach(FLAG ${special_link_flags})
        set(LINK_COMMAND "${LINK_COMMAND} ${FLAG}")
    endforeach()    

    # Add the output file
    set(LINK_COMMAND "${LINK_COMMAND} -o ${output_name}")

    foreach(source ${file_names})
        # Get the relative path to the source and object files
        file(RELATIVE_PATH OBJ_FILE ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${target}.dir/${source}.${OBJ_EXTENSION})

        # Add the source file and the output file
        set(LINK_COMMAND "${LINK_COMMAND} ${OBJ_FILE}")
    endforeach()

    # Add all the specific link
    foreach(LIB ${LIBRARY_ARCHIVE})
        set(LINK_COMMAND "${LINK_COMMAND} ${LIB}")
    endforeach()  

    # Add all the potential library paths
    foreach(LIB_PATH ${USER_LIB_PATHS})
        if(NOT IS_ABSOLUTE ${LIB_PATH})
            file(RELATIVE_PATH LIB_PATH ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_LIST_DIR}/${LIB_PATH})
        endif()
        if(NOT WIN32)
            set(LINK_COMMAND "${LINK_COMMAND} -L${LIB_PATH}")
        else()
            set(LINK_COMMAND "${LINK_COMMAND} -L${LIB_PATH} -Wl,-rpath,${LIB_PATH}")
        endif()
    endforeach()

    # Add all the potential includes
    foreach(LIB ${USER_LIBS})
        set(LINK_COMMAND "${LINK_COMMAND} -l${LIB}")
    endforeach()

    set(LINK_COMMAND "${LINK_COMMAND}" PARENT_SCOPE)

endfunction()

# Windows executable is going to have the .exe extension
if(WIN32)
    set(EXECUTABLE_EXTENSION ".exe")
endif()

# Display the compile instructions in the emulation flow
getCompileCommands("${COMMON_COMPILE_FLAGS}" "${EMULATOR_COMPILE_FLAGS}" "${COMMON_LINK_FLAGS}" "${EMULATOR_LINK_FLAGS}" "${EMULATOR_TARGET}" "${EMULATOR_OUTPUT_NAME}${EXECUTABLE_EXTENSION}")

add_custom_target(  displayEmulationCompileCommands
                    ${CMAKE_COMMAND} -E cmake_echo_color --cyan ""
                    COMMENT "\nTo compile manually:\n${COMPILE_COMMAND}\nTo link manually:\n${LINK_COMMAND}")
add_dependencies(${EMULATOR_TARGET} displayEmulationCompileCommands)

# Display the compile instructions in the simulation flow
getCompileCommands("${COMMON_COMPILE_FLAGS}" "${SIMULATOR_COMPILE_FLAGS}" "${COMMON_LINK_FLAGS}" "${SIMULATOR_LINK_FLAGS}" "${SIMULATOR_TARGET}" "${SIMULATOR_OUTPUT_NAME}${EXECUTABLE_EXTENSION}")

add_custom_target(  displaySimulationCompileCommands
                    ${CMAKE_COMMAND} -E cmake_echo_color --cyan ""
                    COMMENT "\nTo compile manually:\n${COMPILE_COMMAND}\nTo link manually:\n${LINK_COMMAND}")
add_dependencies(${SIMULATOR_TARGET} displaySimulationCompileCommands)

# Display the compile instructions in the report flow
getCompileCommands("${COMMON_COMPILE_FLAGS}" "${REPORT_COMPILE_FLAGS}" "${MODIFIED_COMMON_LINK_FLAGS_REPORT}" "${REPORT_LINK_FLAGS}" "${REPORT_TARGET}" "${REPORT_OUTPUT_NAME}${EXECUTABLE_EXTENSION}")

add_custom_target(  displayReportCompileCommands
                    ${CMAKE_COMMAND} -E cmake_echo_color --cyan ""
                    COMMENT "\nTo compile manually:\n${COMPILE_COMMAND}\nTo link manually:\n${LINK_COMMAND}")
add_dependencies(${REPORT_TARGET} displayReportCompileCommands)

# Display the compile instructions in the fpga flow
getCompileCommands("${COMMON_COMPILE_FLAGS}" "${FPGA_COMPILE_FLAGS}" "${COMMON_LINK_FLAGS}" "${FPGA_LINK_FLAGS}" "${FPGA_TARGET}" "${FPGA_OUTPUT_NAME}${EXECUTABLE_EXTENSION}")

add_custom_target(  displayFPGACompileCommands
                    ${CMAKE_COMMAND} -E cmake_echo_color --cyan ""
                    COMMENT "\nTo compile manually:\n${COMPILE_COMMAND}\nTo link manually:\n${LINK_COMMAND}")
add_dependencies(${FPGA_TARGET} displayFPGACompileCommands)



########## Special commands for printing the fpga_crossgen and fpga_libtool compile instructions

set(COMPILE_COMMAND )
foreach(ARG ${FPGA_CROSSGEN_COMMAND})
 set(COMPILE_COMMAND "${COMPILE_COMMAND} ${ARG}")
endforeach()

add_custom_target(  displayFpgaCrossgenCommand
                    ${CMAKE_COMMAND} -E cmake_echo_color --cyan ""
                    COMMENT "To run fpga_crossgen manually:\n${COMPILE_COMMAND}\n")
add_dependencies(create_rtl_source_object displayFpgaCrossgenCommand)


set(COMPILE_COMMAND )
foreach(ARG ${FPGA_LIBTOOL_COMMAND})
 set(COMPILE_COMMAND "${COMPILE_COMMAND} ${ARG}")
endforeach()

add_custom_target(  displayFpgaLibtoolCommand
                    ${CMAKE_COMMAND} -E cmake_echo_color --cyan ""
                    COMMENT "To run fpga_libtool manually:\n${COMPILE_COMMAND}\n")
add_dependencies(create_library_archive displayFpgaLibtoolCommand)
