├── .github └── workflows │ └── pythonapp.yml ├── LICENSE ├── README.md ├── channel ├── __pycache__ │ └── channel_utils.cpython-37.pyc ├── channel_utils.py └── scribblePad.py └── waveforms ├── DLOFDMvsWOLA.png ├── bitStreampy.py ├── downlink_waveform.py ├── gen_lte_crs.py ├── gen_lte_ss.py ├── modData.txt └── uplink_waveform.py /.github/workflows/pythonapp.yml: -------------------------------------------------------------------------------- 1 | name: Python application 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up Python 3.7 13 | uses: actions/setup-python@v1 14 | with: 15 | python-version: 3.7 16 | - name: Install dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | pip install -r requirements.txt 20 | - name: Lint with flake8 21 | run: | 22 | pip install flake8 23 | # stop the build if there are Python syntax errors or undefined names 24 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 25 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 26 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 27 | - name: Test with pytest 28 | run: | 29 | pip install pytest 30 | pytest 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 siddhant 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 5G_NR_Simulations 2 | 5G New Radio Simulations written in Python with descriptive Plots 3 | Building a detailed Simulation of 5G New Radio Technology along with Wireless channel and System Bourne deformities. 4 | Downlink and Uplink Waveforms have been build. 5 | Currently, working on Channel Models and Defects in System which causes various distortion in the Communication Systems. 6 | 7 | # Plot WaveForm-1 8 | Below is a PSD plot of 5G Downlik Waveform : Blue (Without WOLA) Vs Red (With Wola) 9 | 10 | -------------------------------------------------------------------------------- /channel/__pycache__/channel_utils.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiddhantRaman/5G_NR_Simulations/cc78779e2dff5493cf5c148c4e3627a681fee066/channel/__pycache__/channel_utils.cpython-37.pyc -------------------------------------------------------------------------------- /channel/channel_utils.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # imports 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from scipy import signal 6 | ############################################################################## 7 | ############################################################################## 8 | 9 | ############################################################################## 10 | # 11 | # __________ _______________ 12 | #Transmitted Signal ----> |Multipath| ----> |Changing Gains| ----> 13 | # ---> |+ Signals from Other Users| ---> |+ Broadband Noise| ---> 14 | # ---> |+ Narrowband Noise| ---> Received Signal 15 | # 16 | ############################################################################## 17 | ############################################################################### 18 | #AWGN_NOISE() : Generates Additive White Gaussian Noise of PSD power in dBm/Hz 19 | # AWGN has Gaussian PDF with 0 mean and Sigma^2 = Noise Power(No/2) 20 | # Noise dBm/Hz = 10Log(No/2.BW) 21 | # sigma = sqrt(No/2) = sqrt(BW*10^(Noise dBm/Hz/10)) 22 | ############################################################################### 23 | def awgn_noise(length, power, Bandwidth): 24 | sigma = np.sqrt(Bandwidth * 10**(power/10)) 25 | noise = np.random.normal(0, sigma, length) # Manually set value of Variance to get desired PSD of AWGN Noise 26 | return noise 27 | 28 | ############################################################################### 29 | #LPF Filter() : Generates Low Pass Filter FIR 30 | ############################################################################### 31 | def window_lpf_fir(wc=0.5*np.pi, window='Cos', numTaps=255): 32 | filt = (wc/np.pi) * np.sinc((wc/np.pi)*np.linspace(-(numTaps-1)/2, (numTaps-1)/2, numTaps)) 33 | if window == 'Cos': 34 | filt_win = np.cos(np.linspace(0, np.pi/2, 16))**2 35 | firFilt = np.concatenate((filt[:16]*np.flip(filt_win), filt[16:-16], filt[-16:]*filt_win)) 36 | else: 37 | firFilt = filt 38 | return firFilt 39 | 40 | ############################################################################### 41 | #BPF Filter() : Generates Band Pass Filter FIR 42 | ############################################################################### 43 | def window_bpf_fir(w1=0.25*np.pi, w2=0.5*np.pi, window='Cos', numTaps=255): 44 | filt = (w2/np.pi) * np.sinc((w2/np.pi) * np.linspace(-(numTaps-1)/2, (numTaps-1)/2, numTaps)) - (w1/np.pi) * np.sinc((w1/np.pi) * np.linspace(-(numTaps-1)/2, (numTaps-1)/2, numTaps)) 45 | if window == 'Cos': 46 | filt_win = np.cos(np.linspace(0, np.pi/2, 16))**2 47 | firFilt = np.concatenate((filt[:16]*np.flip(filt_win), filt[16:-16], filt[-16:]*filt_win)) 48 | else: 49 | firFilt = filt 50 | return firFilt 51 | 52 | ############################################################################### 53 | # Delta Function() : Generates Dirac Delta func of length "length" 54 | # x[10] = 1, by default 55 | ############################################################################### 56 | def delta_func(pos=10,length=4096): 57 | Ts = 1/(12*20*15000) 58 | t = np.linspace(0, Ts, num=length, endpoint=False) 59 | delta = np.concatenate((np.zeros(pos), np.ones(1), np.zeros(length-(pos+1)))) 60 | return delta 61 | 62 | ############################################################################### 63 | # Impulse Train Function() : Generates impulse train of length "length" 64 | # gap = 0, length=25, by default 65 | # gap defines intervals of 0s in between 1s 66 | ############################################################################### 67 | def pulse_train(gap=0, length=25): 68 | Ts = 1/(12*20*15000) 69 | t = np.linspace(0, Ts, length, endpoint=False) 70 | x = np.zeros(length) 71 | for i in range(length): 72 | if(i%(gap+1)==0): 73 | x[i] = 1 74 | return x 75 | -------------------------------------------------------------------------------- /channel/scribblePad.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # imports 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from scipy import signal 6 | import channel_utils as cu 7 | ############################################################################## 8 | 9 | ############################################################################### 10 | ############## SCRIPTS #############################///######################## 11 | ############################################################################### 12 | 13 | # Generate AWGN noise of Power -130dBm/Hz and BW 3.6MHz 14 | noise = cu.awgn_noise(300000, -130, 12*20*15000) 15 | fig1 = plt.figure() 16 | 17 | # Plot Noise PSD in Blue 18 | plt.psd(noise, 4096, 12*20*15000) 19 | 20 | # Generate 255 Tap FIR BPF from pi/4 till 3pi/4 with rectangular window in Red 21 | firFilt = cu.window_bpf_fir(0.25*np.pi, 0.75*np.pi, 'None', 255) 22 | # Filter the Noise with this filter 23 | filtered_noise = np.convolve(noise, firFilt, mode='same') 24 | plt.psd(filtered_noise, 4096, 3.6*10**6) 25 | 26 | # Generate 255 Taps FIR BPF from pi/4 till 3pi/4 with Cos^2 Window in Green 27 | firFilt = cu.window_bpf_fir(0.25*np.pi, 0.75*np.pi, 'Cos', 255) 28 | # Filter the Noise with this filter 29 | filtered_noise = np.convolve(noise, firFilt, mode='same') 30 | plt.psd(filtered_noise, 4096, 3.6*10**6) 31 | 32 | # Plot the Un-Filtered Noise and Filtered Noise 33 | plt.title('FIR Filter : Rect Window Vs Cos^2 Window') 34 | 35 | # Plot the spectrum of a delta function : 36 | # 0dBm/Hz constant for all frequencies 37 | #fig2 = plt.figure() 38 | d = cu.delta_func(100,4096) 39 | #print(d.size, d[:110]) 40 | #plt.plot(np.linspace(0, 4095, 4096), 20*np.log10(np.abs(np.fft.fft(d))) ,'-r') 41 | #plt.plot(np.linspace(0, 4095, 4096), np.imag(np.fft.fft(d))/np.real(np.fft.fft(d)), '-g') 42 | 43 | #fig3 = plt.figure() 44 | length = 100 45 | train1 = cu.pulse_train(20, length) 46 | train2 = cu.pulse_train(25, length) 47 | #plt.stem(np.linspace(0, length-1, length), np.abs(np.fft.fft(train1)), '-b') 48 | #plt.stem(np.linspace(0, length-1, length), np.abs(np.fft.fft(train2)), '-r') 49 | 50 | #fig4 = plt.figure() 51 | #plt.stem(np.linspace(0, length-1, length), train1, '-b') 52 | #plt.stem(np.linspace(0, length-1, length), train2, '-r') 53 | 54 | ############## Convolve example ######################### 55 | Ts = 0.01 56 | time = 10 57 | length = int(time/Ts) 58 | t = np.linspace(0, time, num=length, endpoint=False) 59 | h = np.exp(-t) 60 | x = np.zeros(length) 61 | x[int(1/Ts)] = 3 62 | x[int(3/Ts)] = 2 63 | y = np.convolve(h, x, 'full') 64 | fig5 = plt.figure() 65 | plt.stem(t, y[:length], '-c') 66 | plt.stem(t, x, '-r') 67 | plt.stem(t, h, '-g') 68 | plt.show() 69 | -------------------------------------------------------------------------------- /waveforms/DLOFDMvsWOLA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiddhantRaman/5G_NR_Simulations/cc78779e2dff5493cf5c148c4e3627a681fee066/waveforms/DLOFDMvsWOLA.png -------------------------------------------------------------------------------- /waveforms/bitStreampy.py: -------------------------------------------------------------------------------- 1 | # bitStreampy.py 2 | 3 | ################################################################### 4 | # Generates different bitStreams to be used for OFDM symbol data 5 | ################################################################### 6 | import numpy as np 7 | 8 | def random_bit_stream(length=1000000, modulation='BPSK'): 9 | if modulation == 'BPSK': 10 | bitStream = np.random.randint(2, size=length) 11 | bitStream[bitStream == 0] = -1 12 | else: 13 | bitStreamI = np.random.randint(2, size=length) 14 | bitStreamI[bitStreamI == 0] = -1 15 | bitStreamQ = np.random.randint(2, size=length) 16 | bitStreamQ[bitStreamQ == 0] = -1 17 | bitStream = bitStreamI + 1j* bitStreamQ 18 | np.savetxt('modData.txt', bitStream, fmt='%d') 19 | 20 | random_bit_stream(1000000, 'BPSK') 21 | 22 | -------------------------------------------------------------------------------- /waveforms/downlink_waveform.py: -------------------------------------------------------------------------------- 1 | # downlink_waveform.py 2 | 3 | ############################################################################ 4 | # Functions defined here will be used to generate downlink OFDM 5G NR waves 5 | ############################################################################ 6 | import numpy as np 7 | import matplotlib.pyplot as plt 8 | from scipy import signal 9 | 10 | def get_papr(Signal): 11 | length = Signal.size 12 | peakPwr = (np.abs(np.max(Signal)))**2 13 | avgPwr = np.abs(np.inner(Signal, Signal))/length 14 | return (10*np.log10(peakPwr/avgPwr)) 15 | 16 | def overlap_add(S, S_t, WinSize): 17 | WinSize = int(WinSize) 18 | if(WinSize > 0): 19 | retval = np.concatenate((S[:-WinSize], S[-WinSize:] + S_t[:WinSize], S_t[WinSize:])) 20 | else: 21 | retval = np.concatenate((S, S_t)) 22 | return retval 23 | 24 | ######################################################################################################################## 25 | # Function to generate NumOfSymbols OFDM symbol based on waveform specified in 5G 26 | # input params : NumOfSymbols, numerology(mu), Nrb (Resource Blocks), Guard tones on both side of spectrum 27 | # output : NumOfSymbols 5G OFDM symbols with Cyclic Prefix of 288 samples 28 | # Algorithm is simple to understand from the code 29 | # I have used Cosine Windowing for generating WOLA OFDM symbols 30 | ######################################################################################################################## 31 | def ofdm_dl_waveform_5g(NumOfSymbols, mu, Nrb, Guard, WinSize): 32 | #################################################################################################################### 33 | NFFT = 4096 # NFFT fixed in 5G 34 | NoOfCarriers = 12*Nrb # Num Carriers/Tones = 12 * Num Res Blocks 35 | deltaF = 2**mu * 15000 # sub-carrier spacing 2^mu * 15KHz 36 | CPSize = 288 # Cyclic Prefix is 288 37 | CPSize_FirstSymbol = 320 # First Symbol of half SubFrame Cyc Prefix is 320 38 | subFrameDur = 1/1000 # 1msec 39 | FrameDur = 10*subFrameDur # 10 msec 40 | T_symbol = 1/deltaF # OFDM Symbol Duration without CP 41 | deltaF_max = 15000 * 2**5 # 480KHz 42 | deltaF_ref = 15000 # LTE sub-carrier spacing fixed to 15KHz 43 | NFFT_ref = 2048 # NFFT fixed in LTE 44 | 45 | #################################################################################################################### 46 | #Derived Params 47 | Bandwidth = deltaF * NoOfCarriers # Sub-Carrier spacing * num Sub-Carriers 48 | kappa = (deltaF_max*NFFT)/(deltaF_ref*NFFT_ref) # Constant : max Number 5G symbols possible in LTE Symbol Duration 49 | Nu = NFFT_ref * kappa /(2**mu) # Num of OFDM samples in 32 symbols 50 | Ncpmu = 144*kappa/(2**mu) # total CP samples in 32 symbols 51 | Ts = 1/deltaF_max/NFFT # symbolDur if Symbol occupies 480*4096 KHz (Max bandwidth possible in 5G NR with 4096 tones) 52 | Fs = NFFT/T_symbol # Sampling Frequency to achieve desired Symbol Duration 53 | Fc = Bandwidth/2 - Guard*deltaF # Cut-off frequency for LPF FIR design 54 | #################################################################################################################### 55 | 56 | t = np.linspace(0, (Nu+Ncpmu)*Ts, NFFT + CPSize, endpoint=False) # NFFT+CP size time-domain sequence required 0<=t<(Nu+Ncpmu)Ts 57 | data = np.loadtxt('modData.txt') # BPSK modulated random data stored in 'modData.txt' 58 | NumData = NoOfCarriers - 2*Guard - 1 # Num of nPSK symbols which can be loaded on 1 OFDM Symbol 59 | # Window design for WOLA operation to suppress out of band spectral leakage 60 | if(WinSize>1): 61 | alpha = 0.5 62 | nFilt = np.linspace(-np.pi/2, 0, WinSize) 63 | #x = 4*np.pi*Fc/Bandwidth * np.sinc(2*Fc/Bandwidth * nFilt) 64 | #x_win = x * (np.cos(np.pi*alpha*nFilt/T_symbol))/(1 - (4 *(alpha**2) *np.multiply(nFilt,nFilt))/(T_symbol**2) ) 65 | # Below is a most simple Cos-Squared window which will be applied to the time domain OFDM Symbol 66 | x_win = np.multiply(np.cos(nFilt), np.cos(nFilt)) 67 | print(x_win) 68 | # Below Loop will run for Number of OFDM symbols to be generated 69 | for num in range(0, NumOfSymbols): 70 | # a_k is the sequence of nPSK symbols which will be loaded on Sub-carriers(FFT Tones) 71 | a_k = np.concatenate((np.zeros(Guard), data[NumData*num : NumData*num + int((NumData+1)/2)], np.zeros(1))) 72 | a_k = np.concatenate((a_k, data[NumData*num + int((NumData+1)/2):int(NumData) + NumData*num], np.zeros(Guard))) 73 | 74 | # k is sub-carrier index starting from most negative tone (I am generating DC centered OFDM Spectrum) 75 | k = np.linspace(- int(NoOfCarriers/2), int(NoOfCarriers/2)-1, NoOfCarriers) 76 | # S_t is time-domain OFDM symbol with CP. Above loop generates OFDM Symbol with CP appended already :) 77 | S_t = np.zeros(t.size) 78 | for i in k: 79 | S_t = a_k[int(i+NoOfCarriers/2)] * np.exp(1j*2*np.pi*i*deltaF*(t - Ncpmu*Ts)) + S_t 80 | 81 | # Apply windowing (WOLA) only when window size is greater than 1 82 | if(WinSize>1): 83 | S_t = np.concatenate((S_t , S_t[CPSize:CPSize + WinSize])) 84 | #S_t = np.convolve(S_t, x_win, mode='same')/sum(x_win) 85 | S_t[:WinSize] = S_t[:WinSize] * x_win 86 | S_t[-WinSize:] = S_t[-WinSize:] * np.flip(x_win) 87 | if(num == 0): 88 | S = S_t 89 | else: 90 | S = overlap_add(S, S_t, WinSize) 91 | 92 | print(S.size, Nu, Ncpmu) 93 | print(1/deltaF,(Nu + Ncpmu)*Ts) 94 | # small verification of OFDM Receiver with no noise 95 | # Just remove CP and take FFT of Tx Symbol to get the data back 96 | print(a_k[120:220]) 97 | if(WinSize>1): 98 | x1 = np.fft.fft(S[-4096-WinSize:-WinSize], 4096)/4096 99 | else: 100 | x1 = np.fft.fft(S[-4096:], 4096)/4096 101 | print(np.real(x1[:100])) 102 | #fig1 = plt.figure() 103 | #plt.title('Power Spectral Density (OFDM)') 104 | #plt.psd(S, 4096, Fs) 105 | #plt.xlim((-Nrb*7.5*deltaF, Nrb*7.5*deltaF)) 106 | #plt.ylim((-80, -35)) 107 | #plt.show() 108 | return S 109 | 110 | # Lets check our 5G NR Downlink Wave for mu = 1, Nrb = 20, Guard Tones = 7 and Window of length = 128 111 | mu = 1 112 | S1 = ofdm_dl_waveform_5g(14, mu, 20, 7, 0) 113 | S2 = ofdm_dl_waveform_5g(14, mu, 20, 7, 128) 114 | print(get_papr(S2)) 115 | Fs = 4096 * 15000 * 2**mu 116 | fig1 = plt.figure() 117 | plt.title('PSD Comparison OFDM Vs OFDM-WOLA') 118 | plt.set_cmap('jet') 119 | plt.psd(S1, 4096, Fs) 120 | plt.psd(S2, 4096, Fs) 121 | plt.xlim((-2.75*10**6*2**mu, 2.75*10**6*2**mu)) 122 | plt.show() 123 | -------------------------------------------------------------------------------- /waveforms/gen_lte_crs.py: -------------------------------------------------------------------------------- 1 | # Generate DL Reference Sequences 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | # Generate Cell Specific Reference Sequence 5 | # rl,ns(m) = 1/sqrt(2).(1 - 2.c(2m)) +j1/sqrt(2).(1 - 2.c(2m+1)), m = 0,1,2,3,...,2Nrb, max - 1 6 | # c(n) = (x(n+Nc) + y(n+Nc))mod2 7 | # x(n+31) = (x(n+3) + x(n))mod 2 8 | # y(n+31) = (y(n+3) + y(n+2) + y(n+1) + y(n))mod 2 9 | # c_init and Length of the psedo-random sequence (c(n)) needs to be specified 10 | # inputs : Ns : Slot number within a Frame 11 | # L : Lth OFDM Symbol within the slot 12 | # N_CellID : Physical Layer Cell ID 13 | # output : crs_lte : Cell Specific Reference Sequence 14 | 15 | SQRT_2 = np.sqrt(2) 16 | PI = np.pi 17 | 18 | def gen_lte_crs(Ns, L, N_CellID): 19 | N_cp = 1 20 | N_rb_dlmax = 110 21 | c_init = 1024*(7*(Ns +1) + L + 1)*(2*N_CellID + 1) + 2*N_CellID + N_cp 22 | # Generate Gold Sequence of len = 2*N_rb_dlmax 23 | length = 2 * N_rb_dlmax 24 | x = np.zeros(1600 + length) 25 | y = np.zeros(1600 + length) 26 | c_init_tmp = c_init 27 | for n in range(0,31): 28 | y[30-n+1] = np.floor(c_init_tmp/np.power(2,(30-n))) 29 | c_init_tmp = c_init_tmp - np.floor(c_init_tmp/np.power(2,(30-n))) * np.power(2,(30-n)) 30 | x[1] = 1 31 | 32 | for n in range(0, 1600+length-32): 33 | x[n+31+1] = np.mod((x[n+3+1] + x[n+1]), 2) 34 | y[n+31+1] = np.mod((y[n+3+1] + y[n+2+1] + y[n+1+1] + y[n+1]), 2) 35 | c = np.zeros(length) 36 | for n in range(1, length): 37 | c[n] = np.mod((x[n+1600] + y[n+1600]), 2) 38 | 39 | crs = np.zeros(N_rb_dlmax) + 1j*np.zeros(N_rb_dlmax) 40 | for n in range(0, N_rb_dlmax): 41 | crs[n] = 1/SQRT_2 * (1 - 2*c[2*n]) + 1j*(1/SQRT_2 * ( 1 - 2*c[2*n + 1])) 42 | return crs 43 | 44 | crs = gen_lte_crs(4, 4, 10) 45 | print(len(crs)) 46 | print(crs) 47 | plt.scatter(np.real(crs), np.imag(crs)) 48 | plt.show() 49 | -------------------------------------------------------------------------------- /waveforms/gen_lte_ss.py: -------------------------------------------------------------------------------- 1 | # Generate Primary and Secondary Synchronization Sequence 2 | # This file will also contain generation of full frame of OFDM symbols 3 | # PSS and SSS 4 | # Later in the file we will be performing the synchronization for lte 5 | 6 | import numpy as np 7 | import matplotlib.pyplot as plt 8 | 9 | # Define Global Constants 10 | SQRT_2 = np.sqrt(2) 11 | PI = np.pi 12 | ROOT = np.array([25, 29, 34]) 13 | # Primary Synchrnization sequence of LTE is based on Zadoff-chu Sequence of 14 | # length 62 15 | 16 | # Generate Primary Synchronization sequence 17 | # PSS is mapped to 72 Sub-Carriers(6 RBs), centered around DC 18 | # Length 62 Zadoff-chu Sequence 19 | def gen_lte_pss(N_ID_2): 20 | if(N_ID_2 >2 or N_ID_2 <0): 21 | print("ERROR: N_ID_2 can take only values from {0, 1, 2}") 22 | return -1 23 | root_ind = ROOT[N_ID_2] 24 | print(root_ind) 25 | 26 | n = np.linspace(0,30,31) 27 | pss = np.zeros(62) + 1j*np.zeros(62) #including DC 28 | pss[0:31] = np.exp(-1j*PI/63*root_ind*n*(n + np.ones(31))) 29 | 30 | n = np.linspace(31,61,31) 31 | pss[31:62] = np.exp(-1j*PI/63*root_ind*(n + np.ones(31))*(n + 2*np.ones(31))) 32 | 33 | return pss 34 | 35 | # Generate Secondary Synchronization sequence 36 | # SSS is mapped to 72 Sub-carriers(6 RBs), centered around DC 37 | # SSS in Slot0(SubFrame0) is different that SSS in Slot10(SubFrame5) 38 | # Length 62 39 | def gen_lte_sss(N_ID_1, slotNum): 40 | 41 | pss = gen_lte_pss(1) 42 | print(pss) 43 | plt.scatter(np.real(pss), np.imag(pss)) 44 | plt.show() 45 | -------------------------------------------------------------------------------- /waveforms/uplink_waveform.py: -------------------------------------------------------------------------------- 1 | # downlink_waveform.py 2 | 3 | ############################################################################ 4 | # Functions defined here will be used to generate downlink OFDM 5G NR waves 5 | ############################################################################ 6 | import numpy as np 7 | import matplotlib.pyplot as plt 8 | from scipy import signal 9 | 10 | def get_papr(Signal): 11 | length = Signal.size 12 | peakPwr = (np.abs(np.max(Signal)))**2 13 | avgPwr = np.abs(np.inner(Signal, Signal))/length 14 | return (10*np.log10(peakPwr/avgPwr)) 15 | 16 | def overlap_add(S, S_t, WinSize): 17 | WinSize = int(WinSize) 18 | if(WinSize > 0): 19 | retval = np.concatenate((S[:-WinSize], S[-WinSize:] + S_t[:WinSize], S_t[WinSize:])) 20 | else: 21 | retval = np.concatenate((S, S_t)) 22 | return retval 23 | 24 | ######################################################################################################################## 25 | # Function to generate NumOfSymbols OFDM symbol based on waveform specified in 5G 26 | # input params : NumOfSymbols, numerology(mu), Nrb (Resource Blocks), Guard tones on both side of spectrum 27 | # output : NumOfSymbols 5G OFDM symbols with Cyclic Prefix of 288 samples 28 | # Algorithm is simple to understand from the code 29 | # I have used Cosine Windowing for generating WOLA OFDM symbols 30 | ######################################################################################################################## 31 | def ofdm_ul_waveform_5g(NumOfSymbols, mu, Nrb, Guard, WinSize): 32 | #################################################################################################################### 33 | NFFT = 4096 # NFFT fixed in 5G 34 | NoOfCarriers = 12*Nrb # Num Carriers/Tones = 12 * Num Res Blocks 35 | deltaF = 2**mu * 15000 # sub-carrier spacing 2^mu * 15KHz 36 | CPSize = 288 # Cyclic Prefix is 288 37 | CPSize_FirstSymbol = 320 # First Symbol of half SubFrame Cyc Prefix is 320 38 | subFrameDur = 1/1000 # 1msec 39 | FrameDur = 10*subFrameDur # 10 msec 40 | T_symbol = 1/deltaF # OFDM Symbol Duration without CP 41 | deltaF_max = 15000 * 2**5 # 480KHz 42 | deltaF_ref = 15000 # LTE sub-carrier spacing fixed to 15KHz 43 | NFFT_ref = 2048 # NFFT fixed in LTE 44 | 45 | #################################################################################################################### 46 | #Derived Params 47 | Bandwidth = deltaF * NoOfCarriers # Sub-Carrier spacing * num Sub-Carriers 48 | kappa = (deltaF_max*NFFT)/(deltaF_ref*NFFT_ref) # Constant : max Number 5G symbols possible in LTE Symbol Duration 49 | Nu = NFFT_ref * kappa /(2**mu) # Num of OFDM samples in 32 symbols 50 | Ncpmu = 144*kappa/(2**mu) # total CP samples in 32 symbols 51 | Ts = 1/deltaF_max/NFFT # symbolDur if Symbol occupies 480*4096 KHz (Max bandwidth possible in 5G NR with 4096 tones) 52 | Fs = NFFT/T_symbol # Sampling Frequency to achieve desired Symbol Duration 53 | Fc = Bandwidth/2 - Guard*deltaF # Cut-off frequency for LPF FIR design 54 | #################################################################################################################### 55 | 56 | t = np.linspace(0, (Nu+Ncpmu)*Ts, NFFT + CPSize, endpoint=False) # NFFT+CP size time-domain sequence required 0<=t<(Nu+Ncpmu)Ts 57 | data = np.loadtxt('modData.txt') # BPSK modulated random data stored in 'modData.txt' 58 | NumData = NoOfCarriers - 2*Guard - 1 # Num of nPSK symbols which can be loaded on 1 OFDM Symbol 59 | # Window design for WOLA operation to suppress out of band spectral leakage 60 | if(WinSize>1): 61 | alpha = 0.5 62 | nFilt = np.linspace(-np.pi/2, 0, WinSize) 63 | #x = 4*np.pi*Fc/Bandwidth * np.sinc(2*Fc/Bandwidth * nFilt) 64 | #x_win = x * (np.cos(np.pi*alpha*nFilt/T_symbol))/(1 - (4 *(alpha**2) *np.multiply(nFilt,nFilt))/(T_symbol**2) ) 65 | # Below is a most simple Cos-Squared window which will be applied to the time domain OFDM Symbol 66 | x_win = np.multiply(np.cos(nFilt), np.cos(nFilt)) 67 | print(x_win) 68 | # Below Loop will run for Number of OFDM symbols to be generated 69 | for num in range(0, NumOfSymbols): 70 | # a_k is the sequence of nPSK symbols which will be loaded on Sub-carriers(FFT Tones) 71 | a_k = np.concatenate((np.zeros(Guard), data[NumData*num : NumData*num + int((NumData+1)/2)], np.zeros(1))) 72 | a_k = np.concatenate((a_k, data[NumData*num + int((NumData+1)/2):int(NumData) + NumData*num], np.zeros(Guard))) 73 | 74 | # k is sub-carrier index starting from most negative tone (I am generating DC centered OFDM Spectrum) 75 | k = np.linspace(- int(NoOfCarriers/2), int(NoOfCarriers/2)-1, NoOfCarriers) 76 | # S_t is time-domain OFDM symbol with CP. Above loop generates OFDM Symbol with CP appended already :) 77 | S_t = np.zeros(t.size) 78 | for i in k: 79 | S_t = a_k[int(i+NoOfCarriers/2)] * np.exp(1j*2*np.pi*i*deltaF*(t - Ncpmu*Ts)) + S_t 80 | 81 | # Apply windowing (WOLA) only when window size is greater than 1 82 | if(WinSize>1): 83 | S_t = np.concatenate((S_t , S_t[CPSize:CPSize + WinSize])) 84 | #S_t = np.convolve(S_t, x_win, mode='same')/sum(x_win) 85 | S_t[:WinSize] = S_t[:WinSize] * x_win 86 | S_t[-WinSize:] = S_t[-WinSize:] * np.flip(x_win) 87 | if(num == 0): 88 | S = S_t 89 | else: 90 | S = overlap_add(S, S_t, WinSize) 91 | 92 | print(S.size, Nu, Ncpmu) 93 | print(1/deltaF,(Nu + Ncpmu)*Ts) 94 | # small verification of OFDM Receiver with no noise 95 | # Just remove CP and take FFT of Tx Symbol to get the data back 96 | print(a_k[120:220]) 97 | if(WinSize>1): 98 | x1 = np.fft.fft(S[-4096-WinSize:-WinSize], 4096)/4096 99 | else: 100 | x1 = np.fft.fft(S[-4096:], 4096)/4096 101 | print(np.real(x1[:100])) 102 | #fig1 = plt.figure() 103 | #plt.title('Power Spectral Density (OFDM)') 104 | #plt.psd(S, 4096, Fs) 105 | #plt.xlim((-Nrb*7.5*deltaF, Nrb*7.5*deltaF)) 106 | #plt.ylim((-80, -35)) 107 | #plt.show() 108 | return S 109 | 110 | ##################################################################################################### 111 | # UpLink in 5G NR supports 2 types of Waveform : 112 | # ofdm_ul_waveform_5g() implements general OFDM like DL Waveform 113 | # dft_ofdm_ul_waveform_5g() implements DFT-S-OFDM to minimize the PAPR for power limited purposes 114 | ##################################################################################################### 115 | def dft_ofdm_ul_waveform_5g(NumSymbols, mu, Guard, WinSize): 116 | print('TODO Activity!!!') 117 | 118 | # Lets check our 5G NR Downlink Wave for mu = 1, Nrb = 20, Guard Tones = 7 and Window of length = 128 119 | mu = 1 120 | S1 = ofdm_ul_waveform_5g(14, mu, 20, 7, 0) 121 | S2 = ofdm_ul_waveform_5g(14, mu, 20, 7, 128) 122 | print(get_papr(S2)) 123 | Fs = 4096 * 15000 * 2**mu 124 | fig1 = plt.figure() 125 | plt.title('UL-PSD Comparison OFDM Vs OFDM-WOLA') 126 | plt.set_cmap('jet') 127 | plt.psd(S1, 4096, Fs) 128 | plt.psd(S2, 4096, Fs) 129 | plt.xlim((-2.75*10**6*2**mu, 2.75*10**6*2**mu)) 130 | plt.show() 131 | --------------------------------------------------------------------------------