mirror of
https://gitlab.eurecom.fr/oai/openairinterface5g.git
synced 2026-07-13 04:30:28 +00:00
Compare commits
13 Commits
23796f054f
...
oaipylib
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
662a1bd2db | ||
|
|
da8ecda3d0 | ||
|
|
fa739441af | ||
|
|
b5f0853bfa | ||
|
|
919ef73a54 | ||
|
|
3d794f9ba7 | ||
|
|
e081e3a827 | ||
|
|
24c893840e | ||
|
|
86e4d93684 | ||
|
|
49cd0bcb05 | ||
|
|
71f5b18bb3 | ||
|
|
d573653fe4 | ||
|
|
a8bcd1070f |
9
.gitignore
vendored
9
.gitignore
vendored
@@ -33,3 +33,12 @@ nfapi_nr_interface_scf
|
||||
*.log
|
||||
*.out
|
||||
CMakeUserPresets.json
|
||||
oaipylib.egg-info
|
||||
|
||||
#docker build
|
||||
build
|
||||
|
||||
#other gitignores
|
||||
private_folder
|
||||
tmp_comp_results
|
||||
__pycache__
|
||||
@@ -2148,7 +2148,7 @@ if (${T_TRACER})
|
||||
PHY_COMMON PHY PHY_UE PHY_NR PHY_NR_COMMON PHY_NR_UE PHY_RU
|
||||
L2 L2_LTE L2_NR L2_LTE_NR L2_UE NR_L2_UE MAC_NR_COMMON MAC_UE_NR ngap
|
||||
GTPV1U SCTP_CLIENT MME_APP LIB_NAS_UE SIMU
|
||||
dfts config_internals nr_common crc_byte)
|
||||
liboaipy dfts config_internals nr_common crc_byte)
|
||||
if (TARGET ${i})
|
||||
add_dependencies(${i} generate_T)
|
||||
endif()
|
||||
@@ -2202,6 +2202,7 @@ add_subdirectory(openair2)
|
||||
add_subdirectory(openair3)
|
||||
add_subdirectory(radio)
|
||||
add_subdirectory(tests)
|
||||
add_subdirectory(python)
|
||||
|
||||
if(TARGET oai_cuda_lib)
|
||||
message(STATUS "CUDA library 'oai_cuda_lib' found, linking to targets...")
|
||||
@@ -2212,3 +2213,4 @@ endif()
|
||||
if(PACKAGING_LTE OR PACKAGING_NR OR PACKAGING_COMMON OR PACKAGING_USRP OR PACKAGING_PHYSIM)
|
||||
include("tools/packages/packages.cmake")
|
||||
endif()
|
||||
|
||||
|
||||
@@ -121,8 +121,7 @@ t_nrPolar_params *nr_polar_params(int8_t messageType, uint16_t messageLength, ui
|
||||
newPolarInitNode->encoderLength = NR_POLAR_PBCH_E;
|
||||
newPolarInitNode->crcCorrectionBits = NR_POLAR_PBCH_CRC_ERROR_CORRECTION_BITS;
|
||||
newPolarInitNode->crc_generator_matrix = crc24c_generator_matrix(newPolarInitNode->payloadBits); // G_P
|
||||
// printf("Initializing polar parameters for PBCH (K %d, E
|
||||
// %d)\n",newPolarInitNode->payloadBits,newPolarInitNode->encoderLength);
|
||||
//printf("Initializing polar parameters for PBCH (K %d, E %d)\n",newPolarInitNode->payloadBits,newPolarInitNode->encoderLength);
|
||||
|
||||
} else if (messageType == NR_POLAR_DCI_MESSAGE_TYPE) {
|
||||
newPolarInitNode->n_max = NR_POLAR_DCI_N_MAX;
|
||||
|
||||
@@ -126,6 +126,7 @@ void phy_init_nr_gNB(PHY_VARS_gNB *gNB)
|
||||
while(gNB->configured == 0)
|
||||
usleep(10000);
|
||||
|
||||
LOG_I(PHY,"Loading dfts shared lib\n");
|
||||
load_dftslib();
|
||||
|
||||
crcTableInit();
|
||||
|
||||
53
python/CMakeLists.txt
Normal file
53
python/CMakeLists.txt
Normal file
@@ -0,0 +1,53 @@
|
||||
#/*
|
||||
# * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
|
||||
# * contributor license agreements. See the NOTICE file distributed with
|
||||
# * this work for additional information regarding copyright ownership.
|
||||
# * The OpenAirInterface Software Alliance licenses this file to You under
|
||||
# * the OAI Public License, Version 1.1 (the "License"); you may not use this file
|
||||
# * except in compliance with the License.
|
||||
# * You may obtain a copy of the License at
|
||||
# *
|
||||
# * http://www.openairinterface.org/?page_id=698
|
||||
# *
|
||||
# * Unless required by applicable law or agreed to in writing, software
|
||||
# * distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# * See the License for the specific language governing permissions and
|
||||
# * limitations under the License.
|
||||
# *-------------------------------------------------------------------------------
|
||||
# * For more information about the OpenAirInterface (OAI) Software Alliance:
|
||||
# * contact@openairinterface.org
|
||||
# */
|
||||
|
||||
cmake_minimum_required (VERSION 3.19)
|
||||
project (OpenAirInterface LANGUAGES C CXX)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(OAI_VERSION 2.4.0)
|
||||
|
||||
find_package(Python3 REQUIRED COMPONENTS Interpreter Development.Module)
|
||||
|
||||
add_library(liboaipy MODULE
|
||||
src/oaipylib_module.c
|
||||
src/oaipylib_wrappers.c
|
||||
src/oaipylib_top.c
|
||||
${NFAPI_USER_DIR}/nfapi.c
|
||||
${NFAPI_USER_DIR}/gnb_ind_vars.c
|
||||
${PHY_INTERFACE_DIR}/queue_t.c
|
||||
)
|
||||
|
||||
target_include_directories(liboaipy PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
target_include_directories(liboaipy PRIVATE ${Python3_INCLUDE_DIRS})
|
||||
|
||||
target_link_libraries(liboaipy PRIVATE
|
||||
-Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON CONFIG_LIB L2_NR -Wl,--end-group
|
||||
m pthread ${T_LIB} ITTI dl nr_ue_phy_meas physim_common
|
||||
softmodem_common
|
||||
Python3::Module
|
||||
)
|
||||
|
||||
set_target_properties(liboaipy PROPERTIES
|
||||
PREFIX ""
|
||||
OUTPUT_NAME "liboaipy"
|
||||
)
|
||||
123
python/Examples/polartest.py
Normal file
123
python/Examples/polartest.py
Normal file
@@ -0,0 +1,123 @@
|
||||
import math
|
||||
import random
|
||||
import os
|
||||
import sys
|
||||
sys.setdlopenflags(os.RTLD_NOW | os.RTLD_GLOBAL)
|
||||
import oaipylib as oai
|
||||
import numpy as np
|
||||
|
||||
def nr_bit2byte_uint32_8(inp, array_size):
|
||||
array_ind = (array_size + 31) // 32
|
||||
out = [0] * array_size
|
||||
|
||||
for i in range(array_ind - 1):
|
||||
for j in range(32):
|
||||
out[i * 32 + j] = (inp[i] >> j) & 1
|
||||
|
||||
start = (array_ind - 1) * 32
|
||||
for j in range(array_size - start):
|
||||
out[start + j] = (inp[array_ind - 1] >> j) & 1
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def nr_byte2bit_uint8_32(inp, array_size):
|
||||
array_ind = (array_size + 31) // 32
|
||||
out = [0] * array_ind
|
||||
|
||||
for i in range(array_ind):
|
||||
val = 0
|
||||
for j in range(31, 0, -1):
|
||||
idx = i * 32 + j
|
||||
bit = inp[idx] if idx < array_size else 0
|
||||
val |= bit
|
||||
val <<= 1
|
||||
bit0 = inp[i * 32] if i * 32 < array_size else 0
|
||||
val |= bit0
|
||||
out[i] = val & 0xFFFFFFFF
|
||||
|
||||
return out
|
||||
|
||||
def nr_bit2byte_uint32_8(inp, array_size):
|
||||
array_ind = (array_size + 31) // 32
|
||||
out = [0] * array_size
|
||||
|
||||
for i in range(array_ind - 1):
|
||||
for j in range(32):
|
||||
out[i * 32 + j] = (inp[i] >> j) & 1
|
||||
|
||||
start = (array_ind - 1) * 32
|
||||
for j in range(array_size - start):
|
||||
out[start + j] = (inp[array_ind - 1] >> j) & 1
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def bpsk_from_uint32_words(inp, array_size):
|
||||
bits = nr_bit2byte_uint32_8(inp, array_size)
|
||||
scale = 1.0 / math.sqrt(2.0)
|
||||
return [scale if b == 0 else -scale for b in bits]
|
||||
|
||||
|
||||
|
||||
def add_awgn_and_convert_q15(samples, SNRdB):
|
||||
"""
|
||||
Add zero-mean Gaussian noise and convert to signed Q15.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
samples : sequence of float
|
||||
Input real-valued samples, e.g. BPSK symbols.
|
||||
noise_variance : float
|
||||
Variance of the Gaussian noise.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[int]
|
||||
Noisy samples quantized to Q15 int16 values.
|
||||
"""
|
||||
|
||||
SNR_lin = 10**(SNRdB / 10.0)
|
||||
noise_variance = (1 / (2.0 * SNR_lin))
|
||||
if noise_variance < 0:
|
||||
raise ValueError("noise_variance must be non-negative")
|
||||
|
||||
sigma = math.sqrt(noise_variance)
|
||||
out = []
|
||||
|
||||
for x in samples:
|
||||
noisy = x + random.gauss(0.0, sigma)
|
||||
|
||||
# Q15 scaling: float in approximately [-1, 1) -> int8_t range coded in int16_t
|
||||
q15 = int(round(noisy * 8.0))
|
||||
|
||||
# Saturate to int16 range
|
||||
if q15 > 127:
|
||||
q15 = 127
|
||||
elif q15 < -128:
|
||||
q15 = -128
|
||||
|
||||
out.append(q15)
|
||||
|
||||
return out
|
||||
|
||||
## Testing
|
||||
|
||||
oai.init()
|
||||
# this is a 32-bit input with format 0 (PBCH) which has 864 encoded bits
|
||||
encoder_input = np.array([0x12345678],dtype='Q') #dtype=np.ulonglong)
|
||||
mv = memoryview(encoder_input)
|
||||
print(f"Python array format is: {mv.format}")
|
||||
encoded_output = oai.nr_polar_encoder(encoder_input,0,0,0,32,0)
|
||||
bpsk_out = bpsk_from_uint32_words(encoded_output,864)
|
||||
#print(bpsk_out)
|
||||
SNRdB = 0;
|
||||
decoder_input = add_awgn_and_convert_q15(bpsk_out, SNRdB)
|
||||
#print(decoder_input)
|
||||
|
||||
# here we pass the parameters to the OAI polar decoder, decoder_input (Q15 input), ones_flag = 0, messageType=0, messageLength = 864, aggregation_level = 0
|
||||
decoder_output = oai.nr_polar_decoder(decoder_input,0,0,864,0)
|
||||
#print(len(decoder_output))
|
||||
print(hex(decoder_output[0]))
|
||||
|
||||
oai.shutdown()
|
||||
42
python/README.md
Normal file
42
python/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# oaipylib
|
||||
|
||||
Minimal CPython extension skeleton in C for wrapping the OAI C library.
|
||||
|
||||
## Layout
|
||||
|
||||
- `src/oai_lib_api.h`: placeholder header for API into OAI functions
|
||||
- `src/oaipylib_wrappers.h`: Python wrapper declarations
|
||||
- `src/oaipylib_wrappers.c`: Python/C wrapper implementations
|
||||
- `src/oaipylib_module.c`: Python module definition
|
||||
- `setup.py`: setuptools build script
|
||||
- `pyproject.toml`: build-system config
|
||||
|
||||
## Building liboai
|
||||
|
||||
From OAI top-level
|
||||
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -GNinja
|
||||
ninja liboaipy dfts ldpc
|
||||
cp libdfts.so python
|
||||
cp libldpc.so python
|
||||
export LD_LIBRARY_PATH="path to openairinterface5g/build/python":$LD_LIBRARY_PATH
|
||||
```
|
||||
## Build
|
||||
|
||||
|
||||
```bash
|
||||
python -m pip install -U pip setuptools wheel
|
||||
python -m pip install .
|
||||
```
|
||||
|
||||
## To test
|
||||
```bash
|
||||
python Examples/polartest.py
|
||||
```
|
||||
You should see that the output matches the input (0x12345678) at SNR=0dB. Debugging traces will be removed
|
||||
|
||||
## Docker test enviroment
|
||||
There is a work in progress doocker test enviroment located in the dokcer_testenv folder. Currently there is an issue with kernel crashes of jupyter notebooks when running any testing scripts for the OAIPYLIB, these issues could be caused by the docker enviroment, thus it is not recemended to use this envroment for work on the library or any adjacent script. Until the cause of the kernel crashes is confirmed to be unrelated to the docker container setup
|
||||
59
python/docker_testenv/Dockerfile
Normal file
59
python/docker_testenv/Dockerfile
Normal file
@@ -0,0 +1,59 @@
|
||||
FROM ubuntu:24.04
|
||||
|
||||
# Avoid interactive prompts during apt installations
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
software-properties-common \
|
||||
automake \
|
||||
build-essential \
|
||||
cmake \
|
||||
ninja-build \
|
||||
pkg-config \
|
||||
git \
|
||||
libreadline-dev \
|
||||
libconfig-dev \
|
||||
libsctp-dev \
|
||||
libssl-dev \
|
||||
libtool \
|
||||
patch \
|
||||
openssl \
|
||||
zlib1g-dev \
|
||||
xxd \
|
||||
libyaml-cpp-dev \
|
||||
python3-dev \
|
||||
python-is-python3 \
|
||||
python3-pip \
|
||||
python3-venv \
|
||||
python3-numpy \
|
||||
python3-matplotlib \
|
||||
python3-seaborn \
|
||||
python3-pandas \
|
||||
python3-ipykernel \
|
||||
jupyter \
|
||||
bison \
|
||||
flex \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install asn1c from source (as OAI requires a specific version)
|
||||
RUN cd /tmp && \
|
||||
git clone https://github.com/mouse07410/asn1c && \
|
||||
cd asn1c && \
|
||||
git checkout 940dd5fa9f3917913fd487b13dfddfacd0ded06e && \
|
||||
autoreconf -iv && \
|
||||
CFLAGS="-O2 -fno-strict-aliasing" ./configure --prefix /opt/asn1c/ && \
|
||||
make -j$(nproc) && \
|
||||
make install && \
|
||||
ldconfig && \
|
||||
cd / && rm -rf /tmp/asn1c
|
||||
|
||||
# Install simde from source
|
||||
RUN cd /tmp && \
|
||||
git clone https://github.com/simd-everywhere/simde-no-tests.git simde && \
|
||||
cd simde && \
|
||||
git checkout c7f26b73ba8e874b95c2cec2b497826ad2188f68 && \
|
||||
cp -rv ../simde /usr/include && \
|
||||
cd / && rm -rf /tmp/simde
|
||||
|
||||
# Run bash to allow interactive access or sleep
|
||||
CMD ["sleep", "infinity"]
|
||||
23
python/docker_testenv/buildOIA_test.bash
Normal file
23
python/docker_testenv/buildOIA_test.bash
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
rm -rf build
|
||||
mkdir -p build
|
||||
cd build
|
||||
|
||||
cmake .. -GNinja
|
||||
ninja liboaipy dfts ldpc
|
||||
|
||||
install -m 755 libdfts.so python/libdfts.so
|
||||
install -m 755 libldpc.so python/libldpc.so
|
||||
|
||||
# Make liboaipy discoverable without requiring manual LD_LIBRARY_PATH exports.
|
||||
install -m 755 python/liboaipy.so /usr/local/lib/liboaipy.so
|
||||
ldconfig
|
||||
|
||||
# Remove any stale local symlink that can shadow the real Python extension module.
|
||||
rm -f python/oaipylib.so
|
||||
|
||||
cd ../python
|
||||
python3 -m pip install --force-reinstall . --break-system-packages
|
||||
|
||||
9
python/docker_testenv/docker-compose.yml
Normal file
9
python/docker_testenv/docker-compose.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
oai-dev:
|
||||
build: .
|
||||
container_name: oai-dev-ubuntu24
|
||||
volumes:
|
||||
- .:/opt/oai
|
||||
working_dir: /opt/oai
|
||||
command: sleep infinity
|
||||
restart: unless-stopped
|
||||
56
python/docker_testenv/run_in_container.sh
Executable file
56
python/docker_testenv/run_in_container.sh
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/bin/bash
|
||||
|
||||
# A wrapper script to execute a command inside the OAI development container.
|
||||
|
||||
set -e # Exit immediately if a command exits with a non-zero status.
|
||||
|
||||
# 1. Define Container and Project Paths
|
||||
CONTAINER_NAME="oai-dev-ubuntu24"
|
||||
PROJECT_ROOT_IN_CONTAINER="/opt/oai"
|
||||
|
||||
# 2. Check for Script Argument
|
||||
if [ -z "$1" ]; then
|
||||
echo "Error: No script path provided."
|
||||
echo "Usage: $0 <path/to/script_to_execute.py>"
|
||||
echo "Example: $0 python/Examples/polartest.py"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SCRIPT_PATH_ON_HOST=$1
|
||||
|
||||
# 2b. Ensure computation results are written to a dedicated folder in the
|
||||
# directory where this wrapper is executed.
|
||||
RESULTS_DIR_ON_HOST="${PWD}/tmp_comp_results"
|
||||
mkdir -p "${RESULTS_DIR_ON_HOST}"
|
||||
|
||||
# 3. Check if Container is Running
|
||||
# The `docker ps -q` command returns the container ID if it's running, otherwise it's empty.
|
||||
if ! docker ps -q -f "name=${CONTAINER_NAME}" | grep -q .; then
|
||||
echo "Error: Container '${CONTAINER_NAME}' is not running."
|
||||
echo "Please ensure the container is up with: docker-compose up -d"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 4. Construct the Full Path to the Script inside the Container
|
||||
# The volume mounts the project root to /opt/oai, so we prepend that.
|
||||
SCRIPT_PATH_IN_CONTAINER="${PROJECT_ROOT_IN_CONTAINER}/${SCRIPT_PATH_ON_HOST}"
|
||||
RESULTS_DIR_IN_CONTAINER="${PROJECT_ROOT_IN_CONTAINER}/tmp_comp_results"
|
||||
|
||||
# 5. Execute the Script
|
||||
echo "--- Executing '${SCRIPT_PATH_IN_CONTAINER}' in container '${CONTAINER_NAME}' ---"
|
||||
echo "--- Writing computation results to '${RESULTS_DIR_ON_HOST}' ---"
|
||||
echo ""
|
||||
|
||||
# The `docker exec` command runs the python3 interpreter on the script path inside the container.
|
||||
# We set LD_LIBRARY_PATH to make sure the oaipylib shared object is found.
|
||||
OAI_LIB_PATH="${PROJECT_ROOT_IN_CONTAINER}/build/python"
|
||||
PYTHON_SRC_PATH="${PROJECT_ROOT_IN_CONTAINER}/python"
|
||||
docker exec \
|
||||
--workdir "${RESULTS_DIR_IN_CONTAINER}" \
|
||||
--env PYTHONPATH=${OAI_LIB_PATH}:${PYTHON_SRC_PATH} \
|
||||
--env LD_LIBRARY_PATH=${OAI_LIB_PATH} \
|
||||
"${CONTAINER_NAME}" \
|
||||
python3 "${SCRIPT_PATH_IN_CONTAINER}"
|
||||
|
||||
echo ""
|
||||
echo "--- Execution finished ---"
|
||||
110
python/polar_encoder_tests/encoder_SNR_sweep_test.py
Normal file
110
python/polar_encoder_tests/encoder_SNR_sweep_test.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
import random
|
||||
import json
|
||||
sys.setdlopenflags(os.RTLD_NOW | os.RTLD_GLOBAL)
|
||||
import oaipylib as oai
|
||||
import numpy as np
|
||||
|
||||
# --- Test Configuration ---
|
||||
SNR_START = -10.0
|
||||
SNR_END = 5.0
|
||||
SNR_STEP = 0.5
|
||||
SNR_RANGE = np.arange(SNR_START, SNR_END + SNR_STEP, SNR_STEP)
|
||||
|
||||
NUM_RUNS_PER_SNR = 5000
|
||||
ORIGINAL_INPUT = 0x12345678
|
||||
MESSAGE_LENGTH = 32
|
||||
AGGREGATION_LEVELS = [1, 2, 4, 8, 16]
|
||||
MESSAGE_TYPE = 1 # DCI
|
||||
|
||||
# --- Helper Functions ---
|
||||
def bpsk(inp, array_size):
|
||||
np_input = np.array(inp, dtype=np.uint32)
|
||||
bits = ((np_input[:, None] >> np.arange(32, dtype=np.uint32)) & 1).flatten()[:array_size]
|
||||
return (1 - 2 * bits.astype(np.int8)) / np.sqrt(2.0)
|
||||
|
||||
def add_awgn_and_quantize(samples, SNRdB):
|
||||
SNR_lin = 10**(SNRdB / 10.0)
|
||||
noise_variance = (1 / (2.0 * SNR_lin))
|
||||
sigma = math.sqrt(noise_variance)
|
||||
decoder_input = []
|
||||
for x in samples:
|
||||
noisy = x + random.gauss(0.0, sigma)
|
||||
q15 = int(round(noisy * 8.0))
|
||||
if q15 > 127: q15 = 127
|
||||
elif q15 < -128: q15 = -128
|
||||
decoder_input.append(q15)
|
||||
return decoder_input
|
||||
|
||||
# --- Main script ---
|
||||
oai.init()
|
||||
|
||||
|
||||
global_results = {}
|
||||
|
||||
try:
|
||||
for agg_level in AGGREGATION_LEVELS:
|
||||
encoded_len = 108 * agg_level
|
||||
#print(f"\n>> Aggregation Level: {agg_level} (E={encoded_len})")
|
||||
|
||||
encoder_input = np.array([ORIGINAL_INPUT], dtype='Q')
|
||||
encoded_output = oai.nr_polar_encoder(encoder_input, 0, 0, MESSAGE_TYPE, MESSAGE_LENGTH, agg_level)
|
||||
bpsk_symbols = bpsk(encoded_output, encoded_len)
|
||||
|
||||
results = {}
|
||||
for snr in SNR_RANGE:
|
||||
num_failures = 0
|
||||
for _ in range(NUM_RUNS_PER_SNR):
|
||||
decoder_input = add_awgn_and_quantize(bpsk_symbols, snr)
|
||||
decoder_output = oai.nr_polar_decoder(decoder_input, 0, MESSAGE_TYPE, MESSAGE_LENGTH, agg_level)
|
||||
if decoder_output[0] != ORIGINAL_INPUT:
|
||||
num_failures += 1
|
||||
|
||||
bler = num_failures / NUM_RUNS_PER_SNR
|
||||
# Use string key for SNR to make it JSON serializable
|
||||
results[f"{snr:.2f}"] = bler
|
||||
#print(f" SNR: {snr:5.2f} dB | BLER: {bler:.4f}")
|
||||
|
||||
global_results[str(agg_level)] = results
|
||||
|
||||
output_file = "polar_results.json"
|
||||
with open(output_file, 'w') as f:
|
||||
json.dump(global_results, f, indent=4)
|
||||
print(f"\nResults saved to {output_file}")
|
||||
|
||||
# --- Plotting ---
|
||||
try:
|
||||
import matplotlib.pyplot as plt
|
||||
import seaborn as sns
|
||||
import pandas as pd
|
||||
|
||||
|
||||
# Convert results to a DataFrame for easy plotting
|
||||
df = pd.DataFrame(global_results).transpose()
|
||||
df.index.name = 'Aggregation Level'
|
||||
df.columns.name = 'SNR (dB)'
|
||||
# Sort index and columns to ensure correct order
|
||||
df.index = df.index.astype(int)
|
||||
df = df.sort_index()
|
||||
df.columns = df.columns.astype(float)
|
||||
df = df.reindex(sorted(df.columns), axis=1)
|
||||
|
||||
plt.figure(figsize=(12, 8))
|
||||
sns.heatmap(df, annot=False, cmap='plasma', cbar_kws={'label': 'BLER'})
|
||||
plt.title(f"Polar Decoder BLER Heatmap (K={MESSAGE_LENGTH})")
|
||||
plt.ylabel("Aggregation Level (E = 108 * Agg)")
|
||||
plt.xlabel("SNR (dB)")
|
||||
|
||||
plot_file = "polar_bler_heatmap.png"
|
||||
plt.savefig(plot_file)
|
||||
print(f"Heatmap saved to {plot_file}")
|
||||
|
||||
except ImportError:
|
||||
print("\nMatplotlib/Seaborn not found. Skipping plotting.")
|
||||
except Exception as e:
|
||||
print(f"\nPlotting failed: {e}")
|
||||
|
||||
finally:
|
||||
oai.shutdown()
|
||||
187
python/polar_encoder_tests/notebook.ipynb
Normal file
187
python/polar_encoder_tests/notebook.ipynb
Normal file
@@ -0,0 +1,187 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "343fd19f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Testbench notebook work in progress.\n",
|
||||
"\n",
|
||||
"Currently it will **crash the kernel** after second run, when started in the test enviroment docker\n",
|
||||
"\n",
|
||||
"Code in here is the same as other python scripts in this python folder. Until the kernel crashing behavior is fixed, no new code will be writen in the form of a jupyter notebook"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "20c62db9",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Pre-allocating padded host memory for the CPU channel pipeline...\n",
|
||||
"Original input: 0x12345678\n",
|
||||
"Decoded output: 0x12345678\n",
|
||||
"SUCCESS: Decoded output matches original input.\n",
|
||||
"[NR_MAC] TDD period index = 6, based on the sum of dl_UL_TransmissionPeriodicity from Pattern1 (5.000000 ms) and Pattern2 (0.000000 ms): Total = 5.000000 ms\n",
|
||||
"\u001b[0m[NR_MAC] Set TDD configuration period to: 8 DL slots, 3 UL slots, 10 slots per period (NR_TDD_UL_DL_Pattern is 7 DL slots, 2 UL slots, 6 DL symbols, 4 UL symbols)\n",
|
||||
"\u001b[0m[NR_MAC] Configured 1 TDD patterns (total slots: pattern1 = 10, pattern2 = 0)\n",
|
||||
"\u001b[0m[UTIL] threadCreate() for MAC_STATS: creating thread (no affinity, default priority)\n",
|
||||
"\u001b[0m[NR_MAC] Set TX antenna number to 1, Set RX antenna number to 1 (num ssb 8: ff000000,0)\n",
|
||||
"\u001b[0m[NR_MAC] TDD period index = 6, based on the sum of dl_UL_TransmissionPeriodicity from Pattern1 (5.000000 ms) and Pattern2 (0.000000 ms): Total = 5.000000 ms\n",
|
||||
"\u001b[0m[NR_MAC] Set TDD configuration period to: 8 DL slots, 3 UL slots, 10 slots per period (NR_TDD_UL_DL_Pattern is 7 DL slots, 2 UL slots, 6 DL symbols, 4 UL symbols)\n",
|
||||
"\u001b[0m[NR_MAC] Configured 1 TDD patterns (total slots: pattern1 = 10, pattern2 = 0)\n",
|
||||
"\u001b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[LOG] init aborted, configuration couldn't be performed\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[0m[NR_PHY] Set TDD Period Configuration: 2 periods per frame, 20 slots to be configured (8 DL, 3 UL)\n",
|
||||
"\u001b[0m[NR_PHY] TDD period configuration: slot 0 is DOWNLINK\n",
|
||||
"\u001b[0m[NR_PHY] TDD period configuration: slot 1 is DOWNLINK\n",
|
||||
"\u001b[0m[NR_PHY] TDD period configuration: slot 2 is DOWNLINK\n",
|
||||
"\u001b[0m[NR_PHY] TDD period configuration: slot 3 is DOWNLINK\n",
|
||||
"\u001b[0m[NR_PHY] TDD period configuration: slot 4 is DOWNLINK\n",
|
||||
"\u001b[0m[NR_PHY] TDD period configuration: slot 5 is DOWNLINK\n",
|
||||
"\u001b[0m[NR_PHY] TDD period configuration: slot 6 is DOWNLINK\n",
|
||||
"\u001b[0m[NR_PHY] TDD period configuration: slot 7 is FLEXIBLE: DDDDDDFFFFUUUU\n",
|
||||
"\u001b[0m[NR_PHY] TDD period configuration: slot 8 is UPLINK\n",
|
||||
"\u001b[0m[NR_PHY] TDD period configuration: slot 9 is UPLINK\n",
|
||||
"\u001b[0m[PHY] DL frequency 3619080000 Hz, UL frequency 3619080000 Hz: uldl offset 0 Hz\n",
|
||||
"\u001b[0m[PHY] Initializing frame parms for mu 1, N_RB 106, Ncp 0\n",
|
||||
"\u001b[0m[PHY] Init: N_RB_DL 106, first_carrier_offset 1412, nb_prefix_samples 144,nb_prefix_samples0 176, ofdm_symbol_size 2048\n",
|
||||
"\u001b[0m[NR_MAC] TDA index 0: start 0 length 12 k2 6\n",
|
||||
"\u001b[0m[NR_MAC] TDA index 1: start 10 length 3 k2 6\n",
|
||||
"\u001b[0m[NR_MAC] Adding new UE context with RNTI 0x1234\n",
|
||||
"\u001b[0m[NR_MAC] Switching to DL-BWP 1\n",
|
||||
"\u001b[0m[NR_MAC] Switching to UL-BWP 1\n",
|
||||
"\u001b[0m[NR_MAC] Added new UE 1234 with initial CellGroup\n",
|
||||
"\u001b[0m[PHY] L1 configured without analog beamforming\n",
|
||||
"\u001b[0m[PHY] Loading dfts shared lib\n",
|
||||
"\u001b[0mCMDLINE: \"phytest\" \"-O\" \"cmdlineonly::dbgl0\" \n",
|
||||
"[OCM] Channel Model (inside of new_channel_desc_scm)=23\n",
|
||||
"\n",
|
||||
"\u001b[0m[OCM] [CHANNEL] Getting new channel descriptor, nb_tx 1, nb_rx 1, nb_taps 1, channel_length 1\n",
|
||||
"\u001b[0mAWGN: ricean_factor 0.000000\n",
|
||||
"Allocating 614400 samples for txdata\n",
|
||||
"[PHY] Initializing UE vars for gNB TXant 1, UE RXant 1\n",
|
||||
"\u001b[0m[PHY] prs_config configuration NOT found..!! Skipped configuring UE for the PRS reception\n",
|
||||
"\u001b[0m[NR_MAC] [UE0] Initializing MAC\n",
|
||||
"\u001b[0m[RLC] Activated srb0 for UE 0\n",
|
||||
"\u001b[0m[NR_MAC] Initializing dl and ul config_request. num_slots = 20\n",
|
||||
"\u001b[0m[MAC] [UE 0] Applying CellGroupConfig from gNodeB\n",
|
||||
"\u001b[0m\u001b[32m[NR_MAC] Received reconfigurationWithSync\n",
|
||||
"\u001b[0m[NR_MAC] Configuring CRNTI 1234\n",
|
||||
"\u001b[0m[NR_MAC] [UE 0] Set dl_frequency=3600000 kHz (from absoluteFrequencyPointA=640000, band=78, scs=1)\n",
|
||||
"\u001b[0m[NR_MAC] TDD period index = 6, based on the sum of dl_UL_TransmissionPeriodicity from Pattern1 (5.000000 ms) and Pattern2 (0.000000 ms): Total = 5.000000 ms\n",
|
||||
"\u001b[0m[NR_MAC] Set TDD configuration period to: 8 DL slots, 3 UL slots, 10 slots per period (NR_TDD_UL_DL_Pattern is 7 DL slots, 2 UL slots, 6 DL symbols, 4 UL symbols)\n",
|
||||
"\u001b[0m[NR_MAC] Configured 1 TDD patterns (total slots: pattern1 = 10, pattern2 = 0)\n",
|
||||
"\u001b[0mParsing input for py_oaipylib_nr_polar_encoder\n",
|
||||
"Calling oai_lib_nr_polar_encoder(0x2d9f7c00,0,0,0,32,0)\n",
|
||||
"encoded Length 864 (uint32 list size 27), encoded output 303946609(0x121ddb71) (first 32 bits)\n",
|
||||
"Allocating x for input of size 864\n",
|
||||
"Calling oai_lib_nr_polar_decoder with ones_flag 0, messageType 0, messageLength 864, aggregation_level 0\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import math\n",
|
||||
"import os\n",
|
||||
"import sys\n",
|
||||
"import random\n",
|
||||
"sys.setdlopenflags(os.RTLD_NOW | os.RTLD_GLOBAL)\n",
|
||||
"import oaipylib as oai\n",
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def bpsk(inp, array_size):\n",
|
||||
" np_input = np.array(inp, dtype=np.uint32)\n",
|
||||
" # Create an array of shape (num_integers, 32)\n",
|
||||
" # Each row contains the bits of the corresponding integer\n",
|
||||
" bits = ((np_input[:, None] >> np.arange(32, dtype=np.uint32)) & 1).flatten()[:array_size]\n",
|
||||
" return (1 - 2 * bits.astype(np.int8)) / np.sqrt(2.0)\n",
|
||||
"\n",
|
||||
"def awgn(samples, SNRdB):\n",
|
||||
" SNR_lin = 10**(SNRdB / 10.0)\n",
|
||||
" noise_variance = (1 / (2.0 * SNR_lin))\n",
|
||||
" sigma = np.sqrt(noise_variance)\n",
|
||||
" # Use a fixed seed for reproducible results\n",
|
||||
" random.seed(0)\n",
|
||||
" out = []\n",
|
||||
" for x in samples:\n",
|
||||
" noisy = x + random.gauss(0.0, sigma)\n",
|
||||
" q15 = int(round(noisy * 8.0))\n",
|
||||
" if q15 > 127:\n",
|
||||
" q15 = 127\n",
|
||||
" elif q15 < -128:\n",
|
||||
" q15 = -128\n",
|
||||
" out.append(q15)\n",
|
||||
" return np.array(out, dtype=np.int16)\n",
|
||||
"\n",
|
||||
"# --- Test Script ---\n",
|
||||
"\n",
|
||||
"oai.init()\n",
|
||||
"\n",
|
||||
"# 1. Define input data\n",
|
||||
"encoder_input = np.array([0x12345678], dtype='Q')\n",
|
||||
"\n",
|
||||
"# 2. Call the OAI Polar Encoder\n",
|
||||
"encoded_output = oai.nr_polar_encoder(encoder_input, 0, 0, 0, 32, 0)\n",
|
||||
"encoded_len = 864\n",
|
||||
"\n",
|
||||
"# 3. Modulate using BPSK\n",
|
||||
"bpsk_symbols = bpsk(encoded_output, encoded_len)\n",
|
||||
"\n",
|
||||
"# 4. Add AWGN and Quantize\n",
|
||||
"SNRdB = 0\n",
|
||||
"decoder_input = awgn(bpsk_symbols, SNRdB)\n",
|
||||
"\n",
|
||||
"# 5. Call the OAI Polar Decoder\n",
|
||||
"decoder_output = oai.nr_polar_decoder(decoder_input, 0, 0, encoded_len, 0)\n",
|
||||
"\n",
|
||||
"# 6. Print result and shutdown\n",
|
||||
"print(f\"Original input: {hex(encoder_input[0])}\")\n",
|
||||
"print(f\"Decoded output: {hex(decoder_output[0])}\")\n",
|
||||
"if encoder_input[0] == decoder_output[0]:\n",
|
||||
" print(\"SUCCESS: Decoded output matches original input.\")\n",
|
||||
"else:\n",
|
||||
" print(\"FAILURE: Decoded output does not match original input.\")\n",
|
||||
"\n",
|
||||
"oai.shutdown()\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
3
python/pyproject.toml
Normal file
3
python/pyproject.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=61", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
20
python/setup.py
Normal file
20
python/setup.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from setuptools import Extension, setup
|
||||
|
||||
ext = Extension(
|
||||
"oaipylib",
|
||||
sources=[
|
||||
"src/oaipylib_module.c",
|
||||
"src/oaipylib_wrappers.c",
|
||||
],
|
||||
include_dirs=["src"],
|
||||
libraries=["oaipy"],
|
||||
library_dirs=["../build/python"],
|
||||
runtime_library_dirs=["../build/python"],
|
||||
extra_compile_args=["-O2"],
|
||||
)
|
||||
|
||||
setup(
|
||||
name="oaipylib",
|
||||
version="0.1.0",
|
||||
ext_modules=[ext],
|
||||
)
|
||||
33
python/src/oai_lib_api.h
Normal file
33
python/src/oai_lib_api.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef OAI_LIB_API_H
|
||||
#define OAI_LIB_API_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int oai_lib_init(void);
|
||||
void oai_lib_shutdown(void);
|
||||
|
||||
int oai_lib_nr_polar_encoder(uint64_t *A,
|
||||
void **out,
|
||||
int32_t crcmask,
|
||||
uint8_t ones_flag,
|
||||
int8_t messageType,
|
||||
uint16_t messageLength,
|
||||
uint8_t aggregation_level);
|
||||
|
||||
|
||||
int oai_lib_nr_polar_decoder(int16_t *x,
|
||||
uint64_t *out,
|
||||
uint8_t ones_flag,
|
||||
int8_t messageType,
|
||||
uint16_t messageLength,
|
||||
uint8_t aggregation_level);
|
||||
|
||||
const char *oai_lib_last_error(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
43
python/src/oaipylib_module.c
Normal file
43
python/src/oaipylib_module.c
Normal file
@@ -0,0 +1,43 @@
|
||||
#define PY_SSIZE_T_CLEAN
|
||||
#include <Python.h>
|
||||
|
||||
#include "oaipylib_wrappers.h"
|
||||
|
||||
static PyMethodDef oaipylib_methods[] = {
|
||||
{"init", py_oaipylib_init, METH_NOARGS, "Initialize the underlying C library."},
|
||||
{"shutdown", py_oaipylib_shutdown, METH_NOARGS, "Shutdown the underlying C library."},
|
||||
{"nr_polar_encoder", py_oaipylib_nr_polar_encoder, METH_VARARGS, "3GPP NR Polar Encoder"},
|
||||
{"nr_polar_decoder", py_oaipylib_nr_polar_decoder, METH_VARARGS, "16-bit LLR 3GPP NR Polar Decoder"},
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
static struct PyModuleDef oaipylib_module = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"oaipylib",
|
||||
"Python bindings for the OAI C library.",
|
||||
-1,
|
||||
oaipylib_methods
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC PyInit_oaipylib(void) {
|
||||
PyObject *module = PyModule_Create(&oaipylib_module);
|
||||
if (!module) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject *error = PyErr_NewException("oaipylib.Error", NULL, NULL);
|
||||
if (!error) {
|
||||
Py_DECREF(module);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (PyModule_AddObject(module, "Error", error) < 0) {
|
||||
Py_DECREF(error);
|
||||
Py_DECREF(module);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
oaipylib_set_exception_object(error);
|
||||
|
||||
return module;
|
||||
}
|
||||
798
python/src/oaipylib_top.c
Normal file
798
python/src/oaipylib_top.c
Normal file
@@ -0,0 +1,798 @@
|
||||
/*
|
||||
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The OpenAirInterface Software Alliance licenses this file to You under
|
||||
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
|
||||
* except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.openairinterface.org/?page_id=698
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*-------------------------------------------------------------------------------
|
||||
* For more information about the OpenAirInterface (OAI) Software Alliance:
|
||||
* contact@openairinterface.org
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "common/utils/assertions.h"
|
||||
#include "common/utils/nr/nr_common.h"
|
||||
#include "common/utils/var_array.h"
|
||||
#include "executables/nr-uesoftmodem.h"
|
||||
#include "executables/softmodem-common.h"
|
||||
#include "LAYER2/NR_MAC_UE/mac_defs.h"
|
||||
#include "LAYER2/NR_MAC_UE/mac_proto.h"
|
||||
#include "LAYER2/NR_MAC_gNB/mac_proto.h"
|
||||
#include "LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.h"
|
||||
#include "LAYER2/NR_MAC_gNB/nr_mac_gNB.h"
|
||||
#include "LAYER2/NR_MAC_gNB/nr_radio_config.h"
|
||||
#include "NR_BCCH-BCH-Message.h"
|
||||
#include "NR_BWP-Downlink.h"
|
||||
#include "NR_CellGroupConfig.h"
|
||||
#include "NR_MAC_COMMON/nr_mac.h"
|
||||
#include "NR_MAC_COMMON/nr_mac_common.h"
|
||||
#include "NR_PHY_INTERFACE/NR_IF_Module.h"
|
||||
#include "NR_ReconfigurationWithSync.h"
|
||||
#include "NR_ServingCellConfig.h"
|
||||
#include "NR_SetupRelease.h"
|
||||
#include "NR_UE_PHY_INTERFACE/NR_IF_Module.h"
|
||||
#include "PHY/CODING/nrLDPC_coding/nrLDPC_coding_interface.h"
|
||||
#include "PHY/INIT/nr_phy_init.h"
|
||||
#include "PHY/MODULATION/modulation_common.h"
|
||||
#include "PHY/NR_REFSIG/ptrs_nr.h"
|
||||
#include "PHY/NR_TRANSPORT/nr_dlsch.h"
|
||||
#include "PHY/NR_TRANSPORT/nr_transport_common_proto.h"
|
||||
#include "PHY/NR_UE_TRANSPORT/nr_transport_ue.h"
|
||||
#include "PHY/TOOLS/tools_defs.h"
|
||||
#include "PHY/defs_RU.h"
|
||||
#include "PHY/defs_gNB.h"
|
||||
#include "PHY/defs_nr_UE.h"
|
||||
#include "PHY/defs_nr_common.h"
|
||||
#include "PHY/impl_defs_nr.h"
|
||||
#include "PHY/phy_vars_nr_ue.h"
|
||||
#include "SCHED_NR/sched_nr.h"
|
||||
#include "SCHED_NR_UE/defs.h"
|
||||
#include "SCHED_NR_UE/fapi_nr_ue_l1.h"
|
||||
#include "T.h"
|
||||
#include "asn_internal.h"
|
||||
#include "assertions.h"
|
||||
#include "common/config/config_load_configmodule.h"
|
||||
#include "common/ngran_types.h"
|
||||
#include "common/ran_context.h"
|
||||
#include "common/utils/T/T.h"
|
||||
#include "common/utils/nr/nr_common.h"
|
||||
#include "common/utils/var_array.h"
|
||||
#include "common_lib.h"
|
||||
#include "e1ap_messages_types.h"
|
||||
#include "fapi_nr_ue_interface.h"
|
||||
#include "nfapi_interface.h"
|
||||
#include "nfapi_nr_interface.h"
|
||||
#include "nfapi_nr_interface_scf.h"
|
||||
#include "nr_ue_phy_meas.h"
|
||||
#include "oai_asn1.h"
|
||||
#include "openair1/SIMULATION/NR_PHY/nr_unitary_defs.h"
|
||||
#include "openair1/SIMULATION/TOOLS/sim.h"
|
||||
#include "thread-pool.h"
|
||||
#include "time_meas.h"
|
||||
#include "utils.h"
|
||||
#define inMicroS(a) (((double)(a))/(get_cpu_freq_GHz()*1000.0))
|
||||
#include "SIMULATION/LTE_PHY/common_sim.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
PHY_VARS_gNB *gNB;
|
||||
PHY_VARS_NR_UE *UE;
|
||||
RAN_CONTEXT_t RC;
|
||||
int32_t uplink_frequency_offset[MAX_NUM_CCs][4];
|
||||
double cpuf;
|
||||
char *uecap_file;
|
||||
|
||||
//uint8_t nfapi_mode = 0;
|
||||
uint64_t downlink_frequency[MAX_NUM_CCs][4];
|
||||
THREAD_STRUCT thread_struct;
|
||||
nfapi_ue_release_request_body_t release_rntis;
|
||||
//Fixme: Uniq dirty DU instance, by global var, datamodel need better management
|
||||
instance_t DUuniqInstance=0;
|
||||
instance_t CUuniqInstance=0;
|
||||
|
||||
// needed for some functions
|
||||
openair0_config_t openair0_cfg[MAX_CARDS];
|
||||
configmodule_interface_t *uniqCfg = NULL;
|
||||
|
||||
int dummy_nr_ue_ul_indication(nr_uplink_indication_t *ul_info) { return(0); }
|
||||
|
||||
void e1_bearer_context_setup(const e1ap_bearer_setup_req_t *req) { abort(); }
|
||||
void e1_bearer_context_modif(const e1ap_bearer_mod_req_t *req) { abort(); }
|
||||
void e1_bearer_release_cmd(const e1ap_bearer_release_cmd_t *cmd) { abort(); }
|
||||
|
||||
int8_t nr_rrc_RA_succeeded(const module_id_t mod_id, const uint8_t gNB_index) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void processSlotTX(void *arg) {}
|
||||
|
||||
// needed for some functions
|
||||
openair0_config_t openair0_cfg[MAX_CARDS];
|
||||
void update_ptrs_config(NR_CellGroupConfig_t *secondaryCellGroup, uint16_t *rbSize, uint8_t *mcsIndex,int8_t *ptrs_arg);
|
||||
void update_dmrs_config(NR_CellGroupConfig_t *scg, int8_t* dmrs_arg);
|
||||
extern void fix_scd(NR_ServingCellConfig_t *scd);// forward declaration
|
||||
|
||||
|
||||
|
||||
/* specific dlsim DL preprocessor: uses rbStart/rbSize/mcs/nrOfLayers from command line of dlsim */
|
||||
int g_mcsIndex = -1, g_mcsTableIdx = 0, g_rbStart = -1, g_rbSize = -1, g_nrOfLayers = 1, g_pmi = 0;
|
||||
void nr_dlsim_preprocessor(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pp_pdsch)
|
||||
{
|
||||
NR_UE_info_t *UE_info = nr_mac->UE_info.connected_ue_list[0];
|
||||
AssertFatal(nr_mac->UE_info.connected_ue_list[1] == NULL, "Only single UE allowed in dlsim\n");
|
||||
NR_UE_sched_ctrl_t *sched_ctrl = &UE_info->UE_sched_ctrl;
|
||||
NR_UE_DL_BWP_t *current_BWP = &UE_info->current_DL_BWP;
|
||||
NR_ServingCellConfigCommon_t *scc = nr_mac->common_channels[0].ServingCellConfigCommon;
|
||||
|
||||
int nr_of_candidates = 0;
|
||||
if (g_mcsIndex < 4) {
|
||||
find_aggregation_candidates(&sched_ctrl->aggregation_level, &nr_of_candidates, sched_ctrl->search_space, 8);
|
||||
}
|
||||
if (nr_of_candidates == 0) {
|
||||
find_aggregation_candidates(&sched_ctrl->aggregation_level, &nr_of_candidates, sched_ctrl->search_space, 4);
|
||||
}
|
||||
uint32_t Y = get_Y(sched_ctrl->search_space, pp_pdsch->slot, UE_info->rnti);
|
||||
int CCEIndex = find_pdcch_candidate(nr_mac,
|
||||
/* CC_id = */ 0,
|
||||
sched_ctrl->aggregation_level,
|
||||
nr_of_candidates,
|
||||
0,
|
||||
&sched_ctrl->sched_pdcch,
|
||||
sched_ctrl->coreset,
|
||||
Y);
|
||||
AssertFatal(CCEIndex >= 0,
|
||||
"%4d.%2d could not find CCE for DL DCI UE %d/RNTI %04x\n",
|
||||
pp_pdsch->frame,
|
||||
pp_pdsch->slot,
|
||||
0,
|
||||
UE_info->rnti);
|
||||
sched_ctrl->cce_index = CCEIndex;
|
||||
|
||||
NR_sched_pdsch_t sched_pdsch = {
|
||||
.rbStart = g_rbStart,
|
||||
.rbSize = g_rbSize,
|
||||
.bwp_info = get_pdsch_bwp_start_size(nr_mac, UE_info),
|
||||
.mcs = g_mcsIndex,
|
||||
.nrOfLayers = g_nrOfLayers,
|
||||
.pm_index = g_pmi,
|
||||
};
|
||||
/* the following might override the table that is mandated by RRC
|
||||
* configuration */
|
||||
current_BWP->mcsTableIdx = g_mcsTableIdx;
|
||||
sched_pdsch.time_domain_allocation = get_dl_tda(nr_mac, pp_pdsch->slot);
|
||||
AssertFatal(sched_pdsch.time_domain_allocation >= 0,"Unable to find PDSCH time domain allocation in list\n");
|
||||
|
||||
sched_pdsch.tda_info = get_dl_tda_info(current_BWP,
|
||||
sched_ctrl->search_space->searchSpaceType->present,
|
||||
sched_pdsch.time_domain_allocation,
|
||||
NR_MIB__dmrs_TypeA_Position_pos2,
|
||||
1,
|
||||
TYPE_C_RNTI_,
|
||||
sched_ctrl->coreset->controlResourceSetId,
|
||||
false);
|
||||
|
||||
sched_pdsch.dmrs_parms = get_dl_dmrs_params(scc,
|
||||
current_BWP,
|
||||
&sched_pdsch.tda_info,
|
||||
sched_pdsch.nrOfLayers);
|
||||
|
||||
sched_pdsch.Qm = nr_get_Qm_dl(sched_pdsch.mcs, current_BWP->mcsTableIdx);
|
||||
sched_pdsch.R = nr_get_code_rate_dl(sched_pdsch.mcs, current_BWP->mcsTableIdx);
|
||||
sched_pdsch.tb_size = nr_compute_tbs(sched_pdsch.Qm,
|
||||
sched_pdsch.R,
|
||||
sched_pdsch.rbSize,
|
||||
sched_pdsch.tda_info.nrOfSymbols,
|
||||
sched_pdsch.dmrs_parms.N_PRB_DMRS * sched_pdsch.dmrs_parms.N_DMRS_SLOT,
|
||||
0 /* N_PRB_oh, 0 for initialBWP */,
|
||||
0 /* tb_scaling */,
|
||||
sched_pdsch.nrOfLayers) >> 3;
|
||||
|
||||
/* the simulator assumes the HARQ PID is equal to the slot number */
|
||||
sched_pdsch.dl_harq_pid = pp_pdsch->slot;
|
||||
|
||||
/* The scheduler uses lists to track whether a HARQ process is
|
||||
* free/busy/awaiting retransmission, and updates the HARQ process states.
|
||||
* However, in the simulation, we never get ack or nack for any HARQ process,
|
||||
* thus the list and HARQ states don't match what the scheduler expects.
|
||||
* Therefore, below lines just "repair" everything so that the scheduler
|
||||
* won't remark that there is no HARQ feedback */
|
||||
sched_ctrl->feedback_dl_harq.head = -1; // always overwrite feedback HARQ process
|
||||
if (sched_ctrl->harq_processes[pp_pdsch->slot].round == 0) // depending on round set in simulation ...
|
||||
add_front_nr_list(&sched_ctrl->available_dl_harq, pp_pdsch->slot); // ... make PID available
|
||||
else
|
||||
add_front_nr_list(&sched_ctrl->retrans_dl_harq, pp_pdsch->slot); // ... make PID retransmission
|
||||
sched_ctrl->harq_processes[pp_pdsch->slot].is_waiting = false;
|
||||
AssertFatal(sched_pdsch.rbStart >= 0, "invalid rbStart %d\n", sched_pdsch.rbStart);
|
||||
AssertFatal(sched_pdsch.rbSize > 0, "invalid rbSize %d\n", sched_pdsch.rbSize);
|
||||
AssertFatal(sched_pdsch.mcs >= 0, "invalid mcs %d\n", sched_pdsch.mcs);
|
||||
AssertFatal(current_BWP->mcsTableIdx >= 0 && current_BWP->mcsTableIdx <= 2, "invalid mcsTableIdx %d\n", current_BWP->mcsTableIdx);
|
||||
|
||||
post_process_dlsch(nr_mac, pp_pdsch, UE_info, &sched_pdsch);
|
||||
}
|
||||
|
||||
nrUE_params_t nrUE_params;
|
||||
|
||||
nrUE_params_t *get_nrUE_params(void) {
|
||||
return &nrUE_params;
|
||||
}
|
||||
|
||||
void validate_input_pmi(nfapi_nr_config_request_scf_t *gNB_config,
|
||||
nr_pdsch_AntennaPorts_t pdsch_AntennaPorts,
|
||||
int nrOfLayers,
|
||||
int pmi)
|
||||
{
|
||||
if (pmi == 0)
|
||||
return;
|
||||
|
||||
nfapi_nr_pm_pdu_t *pmi_pdu = &gNB_config->pmi_list.pmi_pdu[pmi - 1]; // pmi 0 is identity matrix
|
||||
AssertFatal(pmi == pmi_pdu->pm_idx, "PMI %d doesn't match to the one in precoding matrix %d\n", pmi, pmi_pdu->pm_idx);
|
||||
AssertFatal(nrOfLayers == pmi_pdu->numLayers, "Number of layers %d doesn't match to the one in precoding matrix %d for PMI %d\n",
|
||||
nrOfLayers, pmi_pdu->numLayers, pmi);
|
||||
int num_antenna_ports = pdsch_AntennaPorts.N1 * pdsch_AntennaPorts.N2 * pdsch_AntennaPorts.XP;
|
||||
AssertFatal(num_antenna_ports == pmi_pdu->num_ant_ports, "Configured antenna ports %d does not match precoding matrix AP size %d for PMI %d\n",
|
||||
num_antenna_ports, pmi_pdu->num_ant_ports, pmi);
|
||||
}
|
||||
|
||||
|
||||
float **s_interleaved, **r_re, **r_im;
|
||||
uint8_t n_tx=1,n_rx=1;
|
||||
channel_desc_t *gNB2UE;
|
||||
channel_desc_t *UE2gNB;
|
||||
NR_Sched_Rsp_t *Sched_INFO;
|
||||
c16_t **txdata;
|
||||
|
||||
int oai_lib_init() {
|
||||
|
||||
stop = false;
|
||||
__attribute__((unused)) struct sigaction oldaction;
|
||||
sigaction(SIGINT, &sigint_action, &oldaction);
|
||||
|
||||
FILE *csv_file = NULL;
|
||||
char *filename_csv = NULL;
|
||||
setbuf(stdout, NULL);
|
||||
int c;
|
||||
int i,aa;//,l;
|
||||
double SNR, snr0 = -2.0, snr1 = 2.0;
|
||||
uint8_t snr1set=0;
|
||||
double effRate;
|
||||
//float psnr;
|
||||
double eff_tp_check = 0.7;
|
||||
uint32_t TBS = 0;
|
||||
//double iqim = 0.0;
|
||||
//unsigned char pbch_pdu[6];
|
||||
// int sync_pos, sync_pos_slot;
|
||||
// FILE *rx_frame_file;
|
||||
FILE *output_fd = NULL;
|
||||
//uint8_t write_output_file=0;
|
||||
//int result;
|
||||
//int freq_offset;
|
||||
// int subframe_offset;
|
||||
// char fname[40], vname[40];
|
||||
int trial, n_trials = 1, n_false_positive = 0;
|
||||
//int n_errors2, n_alamouti;
|
||||
uint8_t round;
|
||||
uint8_t num_rounds = 4;
|
||||
char gNBthreads[128]="n";
|
||||
|
||||
//uint32_t nsymb,tx_lev,tx_lev1 = 0,tx_lev2 = 0;
|
||||
//uint8_t extended_prefix_flag=0;
|
||||
//int8_t interf1=-21,interf2=-21;
|
||||
|
||||
FILE *input_fd=NULL,*pbch_file_fd=NULL;
|
||||
//char input_val_str[50],input_val_str2[50];
|
||||
|
||||
//uint8_t frame_mod4,num_pdcch_symbols = 0;
|
||||
|
||||
SCM_t channel_model = AWGN; // AWGN Rayleigh1 Rayleigh1_anticorr;
|
||||
double DS_TDL = .03;
|
||||
int delay = 0;
|
||||
|
||||
//double pbch_sinr;
|
||||
//int pbch_tx_ant;
|
||||
int N_RB_DL=106,mu=1;
|
||||
|
||||
//unsigned char frame_type = 0;
|
||||
|
||||
int frame=1,slot=1;
|
||||
int frame_length_complex_samples;
|
||||
//int frame_length_complex_samples_no_prefix;
|
||||
NR_DL_FRAME_PARMS *frame_parms;
|
||||
UE_nr_rxtx_proc_t UE_proc;
|
||||
gNB_MAC_INST *gNB_mac;
|
||||
NR_UE_MAC_INST_t *UE_mac;
|
||||
int cyclic_prefix_type = NFAPI_CP_NORMAL;
|
||||
int loglvl=OAILOG_INFO;
|
||||
|
||||
//float target_error_rate = 0.01;
|
||||
cpuf = get_cpu_freq_GHz();
|
||||
int8_t enable_ptrs = 0;
|
||||
int8_t modify_dmrs = 0;
|
||||
|
||||
int8_t dmrs_arg[3] = {-1,-1,-1};// Invalid values
|
||||
/* L_PTRS = ptrs_arg[0], K_PTRS = ptrs_arg[1] */
|
||||
int8_t ptrs_arg[2] = {-1,-1};// Invalid values
|
||||
|
||||
uint16_t ptrsRePerSymb = 0;
|
||||
uint16_t pdu_bit_map = 0x0;
|
||||
uint16_t dlPtrsSymPos = 0;
|
||||
uint16_t ptrsSymbPerSlot = 0;
|
||||
uint16_t rbSize = 106;
|
||||
uint8_t mcsIndex = 9;
|
||||
uint8_t dlsch_threads = 0;
|
||||
int chest_type[2] = {0};
|
||||
uint8_t max_ldpc_iterations = 5;
|
||||
|
||||
int print_perf = 0;
|
||||
|
||||
int use_cuda = 0;
|
||||
|
||||
void *h_tx_sig_pinned = NULL;
|
||||
|
||||
#ifdef ENABLE_CUDA
|
||||
void *d_tx_sig = NULL, *d_intermediate_sig = NULL, *d_final_output = NULL;
|
||||
void *d_curand_states = NULL;
|
||||
void *h_final_output_pinned = NULL;
|
||||
void *d_channel_coeffs_gpu = NULL;
|
||||
#endif
|
||||
|
||||
logInit();
|
||||
set_glog(loglvl);
|
||||
/* initialize the sin table */
|
||||
InitSinLUT();
|
||||
#ifdef ENABLE_CUDA
|
||||
init_cuda_chsim_buffers(use_cuda,
|
||||
n_tx,
|
||||
n_rx,
|
||||
&d_tx_sig,
|
||||
&d_intermediate_sig,
|
||||
&d_final_output,
|
||||
&d_curand_states,
|
||||
&h_tx_sig_pinned,
|
||||
&h_final_output_pinned,
|
||||
&d_channel_coeffs_gpu);
|
||||
#endif
|
||||
|
||||
#if !defined(ENABLE_CUDA) || !use_cuda
|
||||
printf("Pre-allocating padded host memory for the CPU channel pipeline...\n");
|
||||
int num_samples_alloc = 153600;
|
||||
const int max_padding_alloc = 256 - 1;
|
||||
size_t padded_tx_alloc_bytes = n_tx * (num_samples_alloc + max_padding_alloc) * 2 * sizeof(float);
|
||||
h_tx_sig_pinned = malloc(padded_tx_alloc_bytes);
|
||||
if (h_tx_sig_pinned == NULL) {
|
||||
printf("Error: Failed to allocate host buffer for CPU path\n");
|
||||
exit(-1);
|
||||
}
|
||||
#endif
|
||||
|
||||
get_softmodem_params()->phy_test = 1;
|
||||
get_softmodem_params()->do_ra = 0;
|
||||
IS_SOFTMODEM_DLSIM = true;
|
||||
RC.gNB = (PHY_VARS_gNB**) malloc(sizeof(PHY_VARS_gNB *));
|
||||
RC.gNB[0] = (PHY_VARS_gNB*) malloc(sizeof(PHY_VARS_gNB ));
|
||||
memset(RC.gNB[0],0,sizeof(PHY_VARS_gNB));
|
||||
|
||||
gNB = RC.gNB[0];
|
||||
gNB->ofdm_offset_divisor = UINT_MAX;
|
||||
gNB->phase_comp = true; // we need to perform phase compensation, otherwise everything will fail
|
||||
frame_parms = &gNB->frame_parms; //to be initialized I suppose (maybe not necessary for PBCH)
|
||||
frame_parms->nb_antennas_tx = n_tx;
|
||||
frame_parms->nb_antennas_rx = n_rx;
|
||||
frame_parms->N_RB_DL = N_RB_DL;
|
||||
frame_parms->N_RB_UL = N_RB_DL;
|
||||
|
||||
AssertFatal((gNB->if_inst = NR_IF_Module_init(0)) != NULL, "Cannot register interface");
|
||||
gNB->if_inst->NR_PHY_config_req = nr_phy_config_request;
|
||||
|
||||
NR_ServingCellConfigCommon_t *scc = calloc(1,sizeof(*scc));;
|
||||
prepare_scc(scc);
|
||||
uint64_t ssb_bitmap = 1; // Enable only first SSB with index ssb_indx=0
|
||||
fill_scc_sim(scc, &ssb_bitmap, N_RB_DL, N_RB_DL, mu, mu);
|
||||
fix_scc(scc, ssb_bitmap);
|
||||
|
||||
frame_structure_t frame_structure = {0};
|
||||
frame_type_t frame_type = TDD;
|
||||
config_frame_structure(mu,
|
||||
scc->tdd_UL_DL_ConfigurationCommon,
|
||||
get_tdd_period_idx(scc->tdd_UL_DL_ConfigurationCommon),
|
||||
frame_type,
|
||||
&frame_structure);
|
||||
AssertFatal(is_dl_slot(slot, &frame_structure), "The slot selected is not DL. Can't run DLSIM\n");
|
||||
|
||||
// TODO do a UECAP for phy-sim
|
||||
nr_pdsch_AntennaPorts_t pdsch_AntennaPorts = {0};
|
||||
pdsch_AntennaPorts.N1 = n_tx > 1 ? n_tx >> 1 : 1;
|
||||
pdsch_AntennaPorts.N2 = 1;
|
||||
pdsch_AntennaPorts.XP = n_tx > 1 ? 2 : 1;
|
||||
const nr_mac_config_t conf = {.pdsch_AntennaPorts = pdsch_AntennaPorts,
|
||||
.pusch_AntennaPorts = n_tx,
|
||||
.minRXTXTIME = 6,
|
||||
.do_CSIRS = 0,
|
||||
.do_SRS = 0,
|
||||
.num_dlharq = 16,
|
||||
.num_ulharq = 16,
|
||||
.maxMIMO_layers = g_nrOfLayers,
|
||||
.force_256qam_off = false,
|
||||
.timer_config.sr_ProhibitTimer = 0,
|
||||
.timer_config.sr_TransMax = 64,
|
||||
.timer_config.sr_ProhibitTimer_v1700 = 0,
|
||||
.timer_config.t300 = 400,
|
||||
.timer_config.t301 = 400,
|
||||
.timer_config.t310 = 2000,
|
||||
.timer_config.n310 = 10,
|
||||
.timer_config.t311 = 3000,
|
||||
.timer_config.n311 = 1,
|
||||
.timer_config.t319 = 400,
|
||||
.num_agg_level_candidates = {0, 0, 1, 1, 0}};
|
||||
const nr_rlc_configuration_t rlc_config = {
|
||||
.srb = {
|
||||
.t_poll_retransmit = 45,
|
||||
.t_reassembly = 35,
|
||||
.t_status_prohibit = 0,
|
||||
.poll_pdu = -1,
|
||||
.poll_byte = -1,
|
||||
.max_retx_threshold = 8,
|
||||
.sn_field_length = 12,
|
||||
},
|
||||
.drb_am = {
|
||||
.t_poll_retransmit = 45,
|
||||
.t_reassembly = 15,
|
||||
.t_status_prohibit = 15,
|
||||
.poll_pdu = 64,
|
||||
.poll_byte = 1024 * 500,
|
||||
.max_retx_threshold = 32,
|
||||
.sn_field_length = 18,
|
||||
},
|
||||
.drb_um = {
|
||||
.t_reassembly = 15,
|
||||
.sn_field_length = 12,
|
||||
}
|
||||
};
|
||||
|
||||
RC.nb_nr_macrlc_inst = 1;
|
||||
RC.nb_nr_mac_CC = (int*)malloc(RC.nb_nr_macrlc_inst*sizeof(int));
|
||||
for (i = 0; i < RC.nb_nr_macrlc_inst; i++)
|
||||
RC.nb_nr_mac_CC[i] = 1;
|
||||
mac_top_init_gNB(ngran_gNB, scc, &conf, &rlc_config);
|
||||
gNB_mac = RC.nrmac[0];
|
||||
nr_mac_config_scc(RC.nrmac[0], scc, &conf);
|
||||
|
||||
gNB_mac->dl_bler.harq_round_max = num_rounds;
|
||||
|
||||
gNB->ap_N1 = pdsch_AntennaPorts.N1;
|
||||
gNB->ap_N2 = pdsch_AntennaPorts.N2;
|
||||
gNB->ap_XP = pdsch_AntennaPorts.XP;
|
||||
|
||||
validate_input_pmi(&gNB_mac->config[0], pdsch_AntennaPorts, g_nrOfLayers, g_pmi);
|
||||
|
||||
NR_UE_NR_Capability_t *UE_Capability_nr = CALLOC(1,sizeof(NR_UE_NR_Capability_t));
|
||||
prepare_sim_uecap(UE_Capability_nr, scc, mu, N_RB_DL, g_mcsTableIdx, 0);
|
||||
rnti_t rnti = 0x1234;
|
||||
int uid = 0;
|
||||
int ssb_index = 0;
|
||||
NR_CellGroupConfig_t *secondaryCellGroup = get_default_secondaryCellGroup(scc, UE_Capability_nr, 0, 1, &conf, uid, ssb_index);
|
||||
secondaryCellGroup->spCellConfig->reconfigurationWithSync = get_reconfiguration_with_sync(rnti, uid, scc, frame);
|
||||
|
||||
/* -U option modify DMRS */
|
||||
if(modify_dmrs) {
|
||||
update_dmrs_config(secondaryCellGroup, dmrs_arg);
|
||||
}
|
||||
/* -T option enable PTRS */
|
||||
if(enable_ptrs) {
|
||||
update_ptrs_config(secondaryCellGroup, &rbSize, &mcsIndex, ptrs_arg);
|
||||
}
|
||||
|
||||
|
||||
//xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, (const void*)secondaryCellGroup);
|
||||
|
||||
// UE dedicated configuration
|
||||
nr_mac_add_test_ue(RC.nrmac[0], rnti, secondaryCellGroup);
|
||||
// reset preprocessor to the one of DLSIM after it has been set during
|
||||
// nr_mac_config_scc()
|
||||
gNB_mac->pre_processor_dl = nr_dlsim_preprocessor;
|
||||
phy_init_nr_gNB(gNB);
|
||||
N_RB_DL = gNB->frame_parms.N_RB_DL;
|
||||
NR_UE_info_t *UE_info = RC.nrmac[0]->UE_info.connected_ue_list[0];
|
||||
|
||||
configure_UE_BWP(RC.nrmac[0], scc, UE_info, false, NR_SearchSpace__searchSpaceType_PR_ue_Specific, -1, -1);
|
||||
|
||||
// stub to configure frame_parms
|
||||
// nr_phy_config_request_sim(gNB,N_RB_DL,N_RB_DL,mu,Nid_cell,SSB_positions);
|
||||
// call MAC to configure common parameters
|
||||
|
||||
/* nr_mac_add_test_ue() has created one user, so set the scheduling
|
||||
* parameters from command line in global variables that will be picked up by
|
||||
* scheduling preprocessor */
|
||||
if (g_mcsIndex < 0) g_mcsIndex = 9;
|
||||
if (g_rbStart < 0) g_rbStart=0;
|
||||
if (g_rbSize < 0) g_rbSize = N_RB_DL - g_rbStart;
|
||||
|
||||
double fs,txbw,rxbw;
|
||||
get_samplerate_and_bw(mu,
|
||||
N_RB_DL,
|
||||
frame_parms->threequarter_fs,
|
||||
&fs,
|
||||
&txbw,
|
||||
&rxbw);
|
||||
|
||||
gNB2UE = new_channel_desc_scm(n_tx,
|
||||
n_rx,
|
||||
channel_model,
|
||||
fs/1e6,//sampling frequency in MHz
|
||||
0,
|
||||
txbw,
|
||||
DS_TDL,
|
||||
0.0,
|
||||
CORR_LEVEL_LOW,
|
||||
0,
|
||||
delay,
|
||||
0,
|
||||
0);
|
||||
|
||||
#ifdef ENABLE_CUDA
|
||||
float *h_channel_coeffs = NULL;
|
||||
if (use_cuda) {
|
||||
int num_links = n_tx * n_rx;
|
||||
h_channel_coeffs = (float *)malloc(num_links * gNB2UE->channel_length * sizeof(float) * 2);
|
||||
}
|
||||
#endif
|
||||
if (gNB2UE==NULL) {
|
||||
printf("Problem generating channel model. Exiting.\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
frame_length_complex_samples = frame_parms->samples_per_subframe*NR_NUMBER_OF_SUBFRAMES_PER_FRAME;
|
||||
//frame_length_complex_samples_no_prefix = frame_parms->samples_per_subframe_wCP*NR_NUMBER_OF_SUBFRAMES_PER_FRAME;
|
||||
int slot_offset = get_samples_slot_timestamp(frame_parms, slot);
|
||||
int slot_length = slot_offset - get_samples_slot_timestamp(frame_parms, slot - 1);
|
||||
|
||||
s_interleaved = malloc(n_tx * sizeof(float *));
|
||||
r_re = malloc(n_rx * sizeof(float *));
|
||||
r_im = malloc(n_rx * sizeof(float *));
|
||||
txdata = malloc(n_tx * sizeof(int *));
|
||||
|
||||
for (i = 0; i < n_tx; i++) {
|
||||
s_interleaved[i] = malloc(slot_length * 2 * sizeof(float));
|
||||
printf("Allocating %d samples for txdata\n", frame_length_complex_samples);
|
||||
txdata[i] = calloc(1, frame_length_complex_samples * sizeof(int));
|
||||
}
|
||||
|
||||
for (i = 0; i < n_rx; i++) {
|
||||
r_re[i] = calloc(1, slot_length * sizeof(float));
|
||||
r_im[i] = calloc(1, slot_length * sizeof(float));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//configure UE
|
||||
UE = malloc(sizeof(PHY_VARS_NR_UE));
|
||||
memset((void*)UE,0,sizeof(PHY_VARS_NR_UE));
|
||||
PHY_vars_UE_g = malloc(sizeof(PHY_VARS_NR_UE**));
|
||||
PHY_vars_UE_g[0] = malloc(sizeof(PHY_VARS_NR_UE*));
|
||||
PHY_vars_UE_g[0][0] = UE;
|
||||
memcpy(&UE->frame_parms,frame_parms,sizeof(NR_DL_FRAME_PARMS));
|
||||
UE->frame_parms.nb_antennas_rx = n_rx;
|
||||
UE->frame_parms.nb_antenna_ports_gNB = n_tx;
|
||||
UE->nrLDPC_coding_interface = gNB->nrLDPC_coding_interface;
|
||||
UE->max_ldpc_iterations = max_ldpc_iterations;
|
||||
init_nr_ue_phy_cpu_stats(&UE->phy_cpu_stats);
|
||||
UE->is_synchronized = 1;
|
||||
|
||||
if (init_nr_ue_signal(UE, 1) != 0)
|
||||
{
|
||||
printf("Error at UE NR initialisation\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
init_nr_ue_transport(UE);
|
||||
|
||||
UE_mac = nr_l2_init_ue(0, mu);
|
||||
ue_init_config_request(UE_mac, get_slots_per_frame_from_scs(mu));
|
||||
|
||||
UE->if_inst = nr_ue_if_module_init(0);
|
||||
UE->if_inst->scheduled_response = nr_ue_scheduled_response;
|
||||
UE->if_inst->phy_config_request = nr_ue_phy_config_request;
|
||||
UE->if_inst->dl_indication = nr_ue_dl_indication;
|
||||
UE->if_inst->ul_indication = dummy_nr_ue_ul_indication;
|
||||
UE->chest_freq = chest_type[0];
|
||||
UE->chest_time = chest_type[1];
|
||||
|
||||
UE_mac->if_module = nr_ue_if_module_init(0);
|
||||
|
||||
|
||||
initFloatingCoresTpool(dlsch_threads, &nrUE_params.Tpool, false, "UE-tpool");
|
||||
|
||||
// generate signal
|
||||
AssertFatal(input_fd==NULL,"Not ready for input signal file\n");
|
||||
|
||||
// clone CellGroup to have a separate copy at UE
|
||||
NR_CellGroupConfig_t *UE_CellGroup = clone_CellGroupConfig(secondaryCellGroup);
|
||||
|
||||
//Configure UE
|
||||
NR_BCCH_BCH_Message_t *mib = get_new_MIB_NR(scc);
|
||||
nr_rrc_mac_config_req_mib(0, 0, mib->message.choice.mib, false);
|
||||
nr_rrc_mac_config_req_cg(0, 0, 0, 0, UE_CellGroup, UE_Capability_nr);
|
||||
|
||||
asn1cFreeStruc(asn_DEF_NR_CellGroupConfig, UE_CellGroup);
|
||||
|
||||
UE_mac->state = UE_CONNECTED;
|
||||
UE_mac->ra.ra_state = nrRA_SUCCEEDED;
|
||||
|
||||
nr_phy_data_t phy_data = {0};
|
||||
fapi_nr_dl_config_request_t dl_config = {.sfn = frame, .slot = slot};
|
||||
nr_scheduled_response_t scheduled_response = {.dl_config = &dl_config, .phy_data = &phy_data, .mac = UE_mac};
|
||||
|
||||
nr_ue_phy_config_request(&UE_mac->phy_config);
|
||||
//NR_COMMON_channels_t *cc = RC.nrmac[0]->common_channels;
|
||||
int ret = 1;
|
||||
initNamedTpool(gNBthreads, &gNB->threadPool, true, "gNB-tpool");
|
||||
initNotifiedFIFO(&gNB->L1_tx_out);
|
||||
|
||||
// Buffers to store internal memory of slot process
|
||||
int rx_size = (((14 * UE->frame_parms.N_RB_DL * 12 * sizeof(int32_t)) + 15) >> 4) << 4;
|
||||
UE->phy_sim_rxdataF = calloc(sizeof(int32_t *) * UE->frame_parms.nb_antennas_rx * g_nrOfLayers,
|
||||
UE->frame_parms.samples_per_slot_wCP * sizeof(int32_t));
|
||||
UE->phy_sim_pdsch_llr = calloc(1, (8 * (3 * 8 * 8448)) * sizeof(int16_t)); // Max length
|
||||
UE->phy_sim_pdsch_rxdataF_ext = calloc(sizeof(int32_t *) * UE->frame_parms.nb_antennas_rx * g_nrOfLayers, rx_size);
|
||||
UE->phy_sim_pdsch_rxdataF_comp = calloc(sizeof(int32_t *) * UE->frame_parms.nb_antennas_rx * g_nrOfLayers, rx_size);
|
||||
UE->phy_sim_pdsch_dl_ch_estimates = calloc(sizeof(int32_t *) * UE->frame_parms.nb_antennas_rx * g_nrOfLayers, rx_size);
|
||||
UE->phy_sim_pdsch_dl_ch_estimates_ext = calloc(sizeof(int32_t *) * UE->frame_parms.nb_antennas_rx * g_nrOfLayers, rx_size);
|
||||
int a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER*NR_MAX_NB_LAYERS; //number of segments to be allocated
|
||||
if (g_rbSize != 273) {
|
||||
a_segments = a_segments*g_rbSize;
|
||||
a_segments = (a_segments/273)+1;
|
||||
}
|
||||
uint32_t dlsch_bytes = a_segments*1056; // allocated bytes per segment
|
||||
UE->phy_sim_dlsch_b = calloc(1, dlsch_bytes);
|
||||
|
||||
// csv file
|
||||
if (filename_csv != NULL) {
|
||||
csv_file = fopen(filename_csv, "a");
|
||||
if (csv_file == NULL) {
|
||||
printf("Can't open file \"%s\", errno %d\n", filename_csv, errno);
|
||||
free(s_interleaved);
|
||||
free(r_re);
|
||||
free(r_im);
|
||||
free(txdata);
|
||||
return 1;
|
||||
}
|
||||
// adding name of parameters into file
|
||||
fprintf(csv_file,"SNR,false_positive,");
|
||||
for (int r = 0; r < num_rounds; r++)
|
||||
fprintf(csv_file,"n_errors_%d,errors_scrambling_%d,channel_bler_%d,channel_ber_%d,",r,r,r,r);
|
||||
fprintf(csv_file,"avg_round,eff_rate,eff_throughput,TBS\n");
|
||||
}
|
||||
//---------------
|
||||
|
||||
Sched_INFO = memalign(32, sizeof(*Sched_INFO));
|
||||
if (Sched_INFO == NULL) {
|
||||
LOG_E(PHY, "out of memory\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void oai_lib_shutdown(void) {
|
||||
|
||||
free(Sched_INFO);
|
||||
|
||||
if (gNB2UE) free_channel_desc_scm(gNB2UE);
|
||||
if (UE2gNB) free_channel_desc_scm(UE2gNB);
|
||||
|
||||
for (int i = 0; i < n_tx; i++) {
|
||||
free(s_interleaved[i]);
|
||||
free(txdata[i]);
|
||||
}
|
||||
for (int i = 0; i < n_rx; i++) {
|
||||
free(r_re[i]);
|
||||
free(r_im[i]);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_CUDA
|
||||
free_cuda_chsim_buffers(use_cuda,
|
||||
&d_tx_sig,
|
||||
&d_intermediate_sig,
|
||||
&d_final_output,
|
||||
&d_curand_states,
|
||||
&h_tx_sig_pinned,
|
||||
&h_final_output_pinned,
|
||||
&h_channel_coeffs,
|
||||
&d_channel_coeffs_gpu);
|
||||
#endif
|
||||
|
||||
free(s_interleaved);
|
||||
free(r_re);
|
||||
free(r_im);
|
||||
free(txdata);
|
||||
free(UE->phy_sim_rxdataF);
|
||||
free(UE->phy_sim_pdsch_llr);
|
||||
free(UE->phy_sim_pdsch_rxdataF_ext);
|
||||
free(UE->phy_sim_pdsch_rxdataF_comp);
|
||||
free(UE->phy_sim_pdsch_dl_ch_estimates);
|
||||
free(UE->phy_sim_pdsch_dl_ch_estimates_ext);
|
||||
free(UE->phy_sim_dlsch_b);
|
||||
|
||||
free_nrLDPC_coding_interface(&gNB->nrLDPC_coding_interface);
|
||||
|
||||
}
|
||||
|
||||
|
||||
int oai_lib_nr_polar_decoder(int16_t *x,
|
||||
uint64_t *out,
|
||||
uint8_t ones_flag,
|
||||
int8_t messageType,
|
||||
uint16_t messageLength,
|
||||
uint8_t aggregation_level) {
|
||||
|
||||
polar_decoder_int16(x, out, ones_flag, messageType, messageLength,aggregation_level);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int oai_lib_nr_polar_encoder(uint64_t *A,
|
||||
void **out,
|
||||
int32_t crcmask,
|
||||
uint8_t ones_flag,
|
||||
int8_t messageType,
|
||||
uint16_t messageLength,
|
||||
uint8_t aggregation_level) {
|
||||
|
||||
// A is the input which is at most 64 bits
|
||||
// out is the rate-matched output, encoded as a bit-packed 32-bit element array. The expected length in bits depends on the messageType:
|
||||
// messageType = 0 => PBCH => output length (bits) = 864 bits (27 32-bit words)
|
||||
// messageType = 1 => DCI => output length (bits) = 108 * aggregation_level
|
||||
// messageType = 2 => PUCCH => output length (bits) = 16 * aggregation_level
|
||||
// messageType = 3 => PSBCH =? output length (bits) = 1792 bits (56 32-bit words))
|
||||
// crc is automatically computed based on the messageLength and messageType and crcmask is applied to the resultant CRC if required. Set to 0 if not required
|
||||
// ones_flag is used to append 24 1's at the beginning of a DCI message prior to computing the CRC parity
|
||||
// aggregation_level is 1,2,4,8,16
|
||||
int encodedLength=0;
|
||||
switch(messageType) {
|
||||
case 0: //PBCH
|
||||
default:
|
||||
encodedLength = 864;
|
||||
break;
|
||||
case 1: //DCI
|
||||
encodedLength = (108*aggregation_level);
|
||||
break;
|
||||
case 2: //UCI/PUCCH
|
||||
encodedLength = (16*aggregation_level);
|
||||
break;
|
||||
case 3: //PSBCH
|
||||
encodedLength = 1792;
|
||||
}
|
||||
*out = malloc(1+(encodedLength>>3));
|
||||
polar_encoder_fast(A,*out,crcmask,ones_flag,messageType,messageLength,aggregation_level);
|
||||
return(encodedLength);
|
||||
}
|
||||
|
||||
const char *oai_lib_last_error(void) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
21
python/src/oaipylib_top.h
Normal file
21
python/src/oaipylib_top.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The OpenAirInterface Software Alliance licenses this file to You under
|
||||
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
|
||||
* except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.openairinterface.org/?page_id=698
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*-------------------------------------------------------------------------------
|
||||
* For more information about the OpenAirInterface (OAI) Software Alliance:
|
||||
* contact@openairinterface.org
|
||||
*/
|
||||
|
||||
185
python/src/oaipylib_wrappers.c
Normal file
185
python/src/oaipylib_wrappers.c
Normal file
@@ -0,0 +1,185 @@
|
||||
#define PY_SSIZE_T_CLEAN
|
||||
#include <Python.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "oaipylib_wrappers.h"
|
||||
#include "oai_lib_api.h"
|
||||
|
||||
#define ARRAY_LENGTH 27
|
||||
|
||||
static PyObject *OaipylibError = NULL;
|
||||
|
||||
void oaipylib_set_exception_object(PyObject *exc) {
|
||||
OaipylibError = exc;
|
||||
}
|
||||
|
||||
PyObject *oaipylib_raise_error(const char *context) {
|
||||
const char *msg = oai_lib_last_error();
|
||||
if (msg == NULL) {
|
||||
msg = "unknown library error";
|
||||
}
|
||||
|
||||
if (OaipylibError) {
|
||||
PyErr_Format(OaipylibError, "%s: %s", context, msg);
|
||||
} else {
|
||||
PyErr_Format(PyExc_RuntimeError, "%s: %s", context, msg);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject *py_oaipylib_init(PyObject *self, PyObject *args) {
|
||||
(void)self;
|
||||
(void)args;
|
||||
|
||||
if (oai_lib_init() != 0) {
|
||||
return oaipylib_raise_error("oai_lib_init failed");
|
||||
}
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject *py_oaipylib_shutdown(PyObject *self, PyObject *args) {
|
||||
(void)self;
|
||||
(void)args;
|
||||
|
||||
oai_lib_shutdown();
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
PyObject *py_oaipylib_nr_polar_encoder(PyObject *self, PyObject *args) {
|
||||
(void)self;
|
||||
|
||||
uint32_t **out; // output is allocated in OAI API function
|
||||
PyObject *out_obj = NULL;
|
||||
PyObject *buffer_obj;
|
||||
Py_buffer py_A;
|
||||
int32_t crcmask;
|
||||
uint8_t ones_flag;
|
||||
uint8_t messageType;
|
||||
uint16_t messageLength;
|
||||
uint8_t aggregation_level;
|
||||
|
||||
printf("Parsing input for py_oaipylib_nr_polar_encoder\n");
|
||||
// parse the buffer object instead
|
||||
if (!PyArg_ParseTuple(args, "OibbHb", &buffer_obj, &crcmask, &ones_flag,&messageType,&messageLength,&aggregation_level)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// extracting information from the buffer
|
||||
if (PyObject_GetBuffer(buffer_obj, &py_A, PyBUF_ANY_CONTIGUOUS | PyBUF_FORMAT)==-1){
|
||||
return NULL;
|
||||
}
|
||||
// check the array dimension
|
||||
if(py_A.ndim != 1) {
|
||||
PyErr_SetString(PyExc_TypeError, "Encoder input is expected to be 1-D array");
|
||||
PyBuffer_Release(&py_A);
|
||||
return NULL;
|
||||
}
|
||||
// check types of the items in the array
|
||||
// add also 'L' ?
|
||||
if (strcmp(py_A.format,"Q") != 0) {
|
||||
PyErr_SetString(PyExc_TypeError, "Expected an array of uint64_t");
|
||||
PyBuffer_Release(&py_A);
|
||||
return NULL;
|
||||
}
|
||||
// pass the raw buffer to the C function
|
||||
out = malloc(sizeof(uint32_t*));
|
||||
printf("Calling oai_lib_nr_polar_encoder(%p,%x,%d,%d,%d,%d)\n", out, crcmask, ones_flag, (int8_t)messageType, messageLength,aggregation_level);
|
||||
int encodedLength = oai_lib_nr_polar_encoder(py_A.buf, (void**)out, crcmask, ones_flag, (int8_t)messageType, messageLength,aggregation_level);
|
||||
if (encodedLength <= 0) return oaipylib_raise_error("oai_lib_nr_polar_encoder failed");
|
||||
|
||||
|
||||
int encodedLength_u32 = (encodedLength>>5) + ((encodedLength&31) > 0 ? 1 : 0);
|
||||
printf("encoded Length %d (uint32 list size %d), encoded output %d(0x%x) (first 32 bits)\n",encodedLength,encodedLength_u32,(*out)[0],(*out)[0]);
|
||||
// once done working with the buffer, release it
|
||||
PyBuffer_Release(&py_A);
|
||||
PyObject *result = PyList_New(encodedLength_u32);
|
||||
if (!result) {
|
||||
free(out);
|
||||
free(*out);
|
||||
return NULL;
|
||||
}
|
||||
for (Py_ssize_t i = 0; i < encodedLength_u32; ++i) {
|
||||
PyObject *value = PyLong_FromUnsignedLong((unsigned long)((*out)[i]));
|
||||
if (!value) {
|
||||
Py_DECREF(result);
|
||||
free(*out);
|
||||
free(out);
|
||||
return NULL;
|
||||
}
|
||||
PyList_SET_ITEM(result, i, value);
|
||||
}
|
||||
free(*out);
|
||||
free(out);
|
||||
return result;
|
||||
}
|
||||
|
||||
PyObject *py_oaipylib_nr_polar_decoder(PyObject *self, PyObject *args) {
|
||||
(void)self;
|
||||
|
||||
PyObject *input_obj = NULL;
|
||||
uint64_t out; // output stored
|
||||
uint8_t ones_flag;
|
||||
int8_t messageType;
|
||||
uint16_t messageLength;
|
||||
uint8_t aggregation_level;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "ObbHb", &input_obj, &ones_flag,&messageType,&messageLength,&aggregation_level)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject *seq = PySequence_Fast(input_obj, "input must be a sequence");
|
||||
if (!seq) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_ssize_t n = PySequence_Fast_GET_SIZE(seq);
|
||||
if (n <= 0) {
|
||||
Py_DECREF(seq);
|
||||
PyErr_SetString(PyExc_ValueError, "input sequence must not be empty");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
printf("Allocating x for input of size %d\n",n);
|
||||
int16_t *x = (int16_t *)malloc((size_t)n * sizeof(int16_t));
|
||||
|
||||
|
||||
if (!x) {
|
||||
Py_DECREF(seq);
|
||||
PyErr_NoMemory();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject **items = PySequence_Fast_ITEMS(seq);
|
||||
for (Py_ssize_t i = 0; i < n; ++i) {
|
||||
x[i] = (int16_t)(PyLong_AsLong(items[i]));
|
||||
if (PyErr_Occurred()) {
|
||||
Py_DECREF(seq);
|
||||
free(x);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
printf("Calling oai_lib_nr_polar_decoder with ones_flag %d, messageType %d, messageLength %d, aggregation_level %d\n",
|
||||
ones_flag,messageType,messageLength,aggregation_level);
|
||||
int rc = oai_lib_nr_polar_decoder(x, &out, ones_flag, messageType, messageLength,aggregation_level);
|
||||
|
||||
Py_DECREF(seq);
|
||||
free(x);
|
||||
|
||||
if (rc != 0) {
|
||||
return oaipylib_raise_error("oai_lib_run_algorithm failed");
|
||||
}
|
||||
|
||||
// printf("output : %x\n",out);
|
||||
PyObject *result = PyList_New(1);
|
||||
if (!result) {
|
||||
return NULL;
|
||||
}
|
||||
PyList_SET_ITEM(result, 0, PyLong_FromUnsignedLongLong((unsigned long long)out));
|
||||
|
||||
return result;
|
||||
}
|
||||
14
python/src/oaipylib_wrappers.h
Normal file
14
python/src/oaipylib_wrappers.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef OAIPYLIB_WRAPPERS_H
|
||||
#define OAIPYLIB_WRAPPERS_H
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
PyObject *py_oaipylib_init(PyObject *self, PyObject *args);
|
||||
PyObject *py_oaipylib_shutdown(PyObject *self, PyObject *args);
|
||||
PyObject *py_oaipylib_nr_polar_encoder(PyObject *self, PyObject *args);
|
||||
PyObject *py_oaipylib_nr_polar_decoder(PyObject *self, PyObject *args);
|
||||
|
||||
PyObject *oaipylib_raise_error(const char *context);
|
||||
void oaipylib_set_exception_object(PyObject *exc);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user