## 3 Software Receiver Implementation
Specifically, the NVIDIA Quadro RTX 4000 GPU, released by NVIDIA at the end of 2018 [27], has been selected. It has been programmed by means of the Computed Unified Device Architecture (CUDA), a C++ based framework, which allows us to develop functions to be executed in parallel on the multiple GPU cores. This NVIDIA GPU comprises 8 GB of memory and is equipped with 2304 CUDA cores, with a clock speed of 1005 MHz. Thanks to CUDA, the GPU is capable of instantiating 1024 threads per block and 65,535 \(\times\) 65,535 blocks per grid, allowing us to parallelize the thread executions on the multiple CUDA cores. The adopted GPU is not the highest-performance GPU available on the market, but it has been chosen to demonstrate the potential of the system implemented on a low-cost device. Indeed, at the time of writing, the cost of this device is lower than one thousand $. The implemented Receiver has been developed on a workstation mounting a commercial CPU, an Intel Core i9-12900K. To implement a flexible and easily maintainable SDR, each block of the Receiver has been developed following a programming paradigm called Factory Method Design Pattern [28]. This is an Object-Oriented Programming (OOP) technique based on the definition of abstract classes that are extended by sub-classes, which determine the nature of the instantiated object. In this way, the receiver blocks have initially been defined as abstract classes, with their specific communication interface, while the implementation is demanded to the programmer, with the possibility of easily employing and testing different solutions for each block. Following this design rule offers multiple benefits:  
* It allows us to change the internal implementation of a block without modifying its interface, i.e., for adopting different algorithms;
* It ensures the possibility of changing each block modifying only a single module of it, without interfering with its entire functionality i.e., without affecting modules inside it or interfacing with it;
* It allows us to enable/disable the hardware acceleration support, creating both serial and parallelized code versions, for debugging and performance purposes;
* It compiles only a portion of the entire system, encapsulating possible errors inside the newly developed module and reducing the management time due to bug fixes;
* It allows for the extension of the developed blocks for eventually implementing new communication protocols, exploiting the same logical structure. Thanks to this approach, the receiver has been implemented to be extremely flexible and modular, allowing alternating parallel code, executed on GPU, to serial code, executed on CPU. At the end of the day, the receiver was implemented as a class object, instantiating each single submodule and exploiting the Factory Method Design Pattern. The architecture of the receiver, shown in Figure 2, has been logically divided into two main parts: the digital front-end section and the back-end section. The first one contains all the processing blocks that are responsible for recovering the signal impairments (i.e., from _Frequency Correction_ block to _Digital AGC_ block), whereas the back-end section only includes the generation and decoding of the soft-bit information (i.e., the _Constellation Demapper_ and the _SCCC Decoder_ blocks).