Merge remote-tracking branch 'origin/issue-955' into integration_2025_w31 (!3545)
Fix overflow when negating minimum int16_t value in precoding This closes #955 It prevents undefined behavior caused by the minimum int16_t value (-32768) in two's complement representation. Because possible negation by multiplying with -1 during precoding cause an overflows since +32768 cannot be represented in 16-bit signed integers and end up again in -32768 because of overflow, here reveald by using the SIMD functions. Workaround we use a slightly smaller number than 1 in Q15 Format to prevent this error. Q15 representation of (integer value -32767) -1 + 2^-15 = -0.999969482421875
This commit is contained in:
@@ -2,3 +2,4 @@ add_subdirectory(nr_phy_common)
|
|||||||
add_subdirectory(TOOLS)
|
add_subdirectory(TOOLS)
|
||||||
add_subdirectory(NR_TRANSPORT)
|
add_subdirectory(NR_TRANSPORT)
|
||||||
add_subdirectory(CODING)
|
add_subdirectory(CODING)
|
||||||
|
add_subdirectory(MODULATION)
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
if (ENABLE_TESTS)
|
||||||
|
add_subdirectory(tests)
|
||||||
|
endif()
|
||||||
@@ -765,9 +765,8 @@ void nr_layer_precoder_simd(const int n_layers,
|
|||||||
// Multiplication and shift
|
// Multiplication and shift
|
||||||
const simde__m256i reals =
|
const simde__m256i reals =
|
||||||
simde_mm256_srai_epi32(simde_mm256_madd_epi16(x, w_c), 15); // (int32_t) .r = (x.r * w.r - x.i * w.i) >> 15
|
simde_mm256_srai_epi32(simde_mm256_madd_epi16(x, w_c), 15); // (int32_t) .r = (x.r * w.r - x.i * w.i) >> 15
|
||||||
const simde__m256i imags = simde_mm256_slli_epi32(
|
const simde__m256i imags =
|
||||||
simde_mm256_madd_epi16(x, w_s),
|
simde_mm256_slli_epi32(simde_mm256_madd_epi16(x, w_s), 1); // (int32_t) .i = (x.r * w.i + x.i * w.r) << 1, since higher 16 bit of each 32 bit is taken by blend_epi16
|
||||||
1); // (int32_t) .i = (x.r * w.i + x.i * w.r) << 1, since higher 16 bit of each 32 bit is taken by blend_epi16
|
|
||||||
|
|
||||||
// Re-arrange to match c16_t format
|
// Re-arrange to match c16_t format
|
||||||
const simde__m256i produ = simde_mm256_blend_epi16(reals, imags, 0xAA);
|
const simde__m256i produ = simde_mm256_blend_epi16(reals, imags, 0xAA);
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
add_executable(test_nr_modulation test_nr_modulation.cpp)
|
||||||
|
target_link_libraries(test_nr_modulation PRIVATE PHY_NR GTest::gtest minimal_lib)
|
||||||
|
add_dependencies(tests test_nr_modulation)
|
||||||
|
add_test(NAME test_nr_modulation
|
||||||
|
COMMAND ./test_nr_modulation)
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Initial unit test for nr_layer_precoder_simd_[cm, simd] functions
|
||||||
|
// Issue: gNB: SIMD Precoding Bug in nr_layer_precoder_simd for PMI = 4
|
||||||
|
// https://gitlab.eurecom.fr/oai/openairinterface5g/-/issues/955
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <simde/x86/avx512.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "openair1/PHY/TOOLS/tools_defs.h" // c16_t
|
||||||
|
#include "nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface_scf.h" // nfapi_nr_pm_pdu_t
|
||||||
|
#include "nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h" // fapi_nr_dl_config_dci_dl_pdu_rel15_t
|
||||||
|
|
||||||
|
//#include "openair1/PHY/MODULATION/nr_modulation.h" // nr_layer_precoder_simd_[cm, simd]
|
||||||
|
|
||||||
|
// Forward declare the function but hide the Variable-Length Array (VLA) feature from C not available in C++
|
||||||
|
c16_t nr_layer_precoder_cm(int n_layers,
|
||||||
|
int symbol_size,
|
||||||
|
c16_t (*dataF_in)[/*symbol_size*/], // c16_t dataF_in[n_layers][symbol_size]
|
||||||
|
int ant,
|
||||||
|
nfapi_nr_pm_pdu_t *pmi_pdu,
|
||||||
|
int offset);
|
||||||
|
|
||||||
|
// Forward declare the function but hide the Variable-Length Array (VLA) feature from C not available in C++
|
||||||
|
void nr_layer_precoder_simd(int n_layers,
|
||||||
|
int symbol_size,
|
||||||
|
const c16_t (*dataF_in)[/*symbol_size*/], // c16_t dataF_in[n_layers][symbol_size]
|
||||||
|
int ant,
|
||||||
|
const nfapi_nr_pm_pdu_t *pmi_pdu,
|
||||||
|
int offset,
|
||||||
|
int re_cnt,
|
||||||
|
c16_t *dataF_out);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TEST(NrLayerPrecoderTest, Basic)
|
||||||
|
{
|
||||||
|
constexpr int n_layers = 2;
|
||||||
|
constexpr int symbol_size = 24;
|
||||||
|
constexpr int n_ants = 2;
|
||||||
|
constexpr int ant = 1;
|
||||||
|
constexpr int re_cnt = 24;
|
||||||
|
|
||||||
|
// Initialize the 2D input data buffer
|
||||||
|
std::vector<c16_t> buffer_in(n_layers * symbol_size);
|
||||||
|
std::vector<c16_t> buffer_out(n_layers * symbol_size);
|
||||||
|
|
||||||
|
for (int i = 0; i < n_layers * symbol_size; ++i) {
|
||||||
|
buffer_in[i] = {static_cast<int16_t>(i + 1), static_cast<int16_t>(-i - 1)};
|
||||||
|
}
|
||||||
|
for (int i = 0; i < n_ants * symbol_size; ++i) {
|
||||||
|
buffer_out[i] = {static_cast<int16_t>(0), static_cast<int16_t>(0)};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cast flat buffer to the required 2D Variable-Length Array (VLA) style pointer
|
||||||
|
c16_t(*dataF_in)[symbol_size] = reinterpret_cast<c16_t(*)[symbol_size]>(buffer_in.data());
|
||||||
|
c16_t(*dataF_out)[symbol_size] = reinterpret_cast<c16_t(*)[symbol_size]>(buffer_out.data());
|
||||||
|
|
||||||
|
// Create and populate the pmi_pdu structure
|
||||||
|
nfapi_nr_pm_pdu_t pmi_pdu = {0};
|
||||||
|
for (int layer = 0; layer < n_layers; ++layer) {
|
||||||
|
pmi_pdu.weights[layer][ant].precoder_weight_Re = -SHRT_MAX; // Q15 representation of (~-1.0) -0.999969482421875 (-1+2^-15)
|
||||||
|
pmi_pdu.weights[layer][ant].precoder_weight_Im = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the C function
|
||||||
|
for (int symbol = 0; symbol < re_cnt; symbol++)
|
||||||
|
dataF_out[ant][symbol] = nr_layer_precoder_cm(n_layers, symbol_size, dataF_in, ant, &pmi_pdu, symbol);
|
||||||
|
|
||||||
|
// You can calculate the expected value manually or test rough correctness
|
||||||
|
for (int symbol = 0; symbol < re_cnt; symbol++) {
|
||||||
|
int result_real = -26 - (symbol<<1);
|
||||||
|
int result_imag = -24 - (symbol<<1);
|
||||||
|
|
||||||
|
EXPECT_EQ(dataF_out[ant][symbol].r, result_real)
|
||||||
|
<< " at [" << ant << "][" << symbol << "] got real part: " << dataF_out[ant][symbol].r;
|
||||||
|
EXPECT_EQ(dataF_out[ant][symbol].i, -result_imag)
|
||||||
|
<< " at [" << ant << "][" << symbol << "] got imag part: " << dataF_out[ant][symbol].i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NrLayerPrecoderTest, SIMD)
|
||||||
|
{
|
||||||
|
constexpr int n_layers = 2;
|
||||||
|
constexpr int symbol_size = 24;
|
||||||
|
constexpr int n_ants = 2;
|
||||||
|
constexpr int ant = 1;
|
||||||
|
constexpr int re_cnt = 24;
|
||||||
|
|
||||||
|
// Initialize the 2D input data buffer
|
||||||
|
std::vector<c16_t> buffer_in(n_layers * symbol_size);
|
||||||
|
std::vector<c16_t> buffer_out(n_layers * symbol_size);
|
||||||
|
|
||||||
|
for (int i = 0; i < n_layers * symbol_size; ++i) {
|
||||||
|
buffer_in[i] = {static_cast<int16_t>(i + 1), static_cast<int16_t>(-i - 1)};
|
||||||
|
}
|
||||||
|
for (int i = 0; i < n_ants * symbol_size; ++i) {
|
||||||
|
buffer_out[i] = {static_cast<int16_t>(0), static_cast<int16_t>(0)};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cast flat buffer to the required 2D Variable-Length Array (VLA) style pointer
|
||||||
|
c16_t(*dataF_in)[symbol_size] = reinterpret_cast<c16_t(*)[symbol_size]>(buffer_in.data());
|
||||||
|
c16_t(*dataF_out)[symbol_size] = reinterpret_cast<c16_t(*)[symbol_size]>(buffer_out.data());
|
||||||
|
|
||||||
|
// Create and populate the pmi_pdu structure
|
||||||
|
nfapi_nr_pm_pdu_t pmi_pdu = {0};
|
||||||
|
for (int layer = 0; layer < n_layers; ++layer) {
|
||||||
|
pmi_pdu.weights[layer][ant].precoder_weight_Re = -SHRT_MAX; // Q15 representation of (~-1.0) -0.999969482421875 (-1+2^-15)
|
||||||
|
pmi_pdu.weights[layer][ant].precoder_weight_Im = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the C function
|
||||||
|
nr_layer_precoder_simd(n_layers, symbol_size, dataF_in, ant, &pmi_pdu, 0, re_cnt, dataF_out[ant]);
|
||||||
|
|
||||||
|
// You can calculate the expected value manually or test rough correctness
|
||||||
|
for (int symbol = 0; symbol < re_cnt; symbol++) {
|
||||||
|
int result_real = -26 - (symbol<<1);
|
||||||
|
int result_imag = -24 - (symbol<<1);
|
||||||
|
|
||||||
|
EXPECT_EQ(dataF_out[ant][symbol].r, result_real)
|
||||||
|
<< " at [" << ant << "][" << symbol << "] got real part: " << dataF_out[ant][symbol].r;
|
||||||
|
EXPECT_EQ(dataF_out[ant][symbol].i, -result_imag)
|
||||||
|
<< " at [" << ant << "][" << symbol << "] got imag part: " << dataF_out[ant][symbol].i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NrLayerPrecoderTest, Compare_CM_SIMD)
|
||||||
|
{
|
||||||
|
constexpr int n_layers = 1;
|
||||||
|
constexpr int symbol_size = 24;
|
||||||
|
constexpr int n_ants = 2;
|
||||||
|
constexpr int re_cnt = 24;
|
||||||
|
|
||||||
|
// Initialize the 2D input data buffer
|
||||||
|
std::vector<c16_t> buffer_in(n_layers * symbol_size);
|
||||||
|
std::vector<c16_t> buffer_out_cm(n_ants * symbol_size);
|
||||||
|
std::vector<c16_t> buffer_out_simd(n_ants * symbol_size);
|
||||||
|
|
||||||
|
for (int i = 0; i < n_layers * symbol_size; ++i) {
|
||||||
|
buffer_in[i] = {static_cast<int16_t>((rand() % (2 * SHRT_MAX + 1)) - SHRT_MAX), static_cast<int16_t>((rand() % (2 * SHRT_MAX + 1)) - SHRT_MAX)};
|
||||||
|
}
|
||||||
|
for (int i = 0; i < n_ants * symbol_size; ++i) {
|
||||||
|
buffer_out_cm[i] = {static_cast<int16_t>(0), static_cast<int16_t>(0)};
|
||||||
|
buffer_out_simd[i] = {static_cast<int16_t>(0), static_cast<int16_t>(0)};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cast flat buffer to the required 2D Variable-Length Array (VLA) style pointer
|
||||||
|
c16_t(*dataF_in)[symbol_size] = reinterpret_cast<c16_t(*)[symbol_size]>(buffer_in.data());
|
||||||
|
c16_t(*dataF_out_cm)[symbol_size] = reinterpret_cast<c16_t(*)[symbol_size]>(buffer_out_cm.data());
|
||||||
|
c16_t(*dataF_out_simd)[symbol_size] = reinterpret_cast<c16_t(*)[symbol_size]>(buffer_out_simd.data());
|
||||||
|
|
||||||
|
// Create and populate the pmi_pdu structure
|
||||||
|
nfapi_nr_pm_pdu_t pmi_pdu = {0};
|
||||||
|
pmi_pdu.pm_idx = 4;
|
||||||
|
for (int layer = 0; layer < n_layers; ++layer) {
|
||||||
|
// Could not use convert_precoder_weight() as complex.h could not be used in googletest
|
||||||
|
// Use the logic in convert_precoder_weight()
|
||||||
|
// precoder [−1,−j]
|
||||||
|
pmi_pdu.weights[layer][0].precoder_weight_Re = -SHRT_MAX; // Q15 representation of (~-1.0) -0.999969482421875 (-1+2^-15)
|
||||||
|
pmi_pdu.weights[layer][0].precoder_weight_Im = 0x0000; // Q15 representation of 0
|
||||||
|
pmi_pdu.weights[layer][1].precoder_weight_Re = 0x0000; // Q15 representation of 0
|
||||||
|
pmi_pdu.weights[layer][1].precoder_weight_Im = -SHRT_MAX; // Q15 representation of (~-1.0) -0.999969482421875 (-1+2^-15)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the results for all the antenna
|
||||||
|
for (int ant = 0; ant < n_ants; ant++) {
|
||||||
|
|
||||||
|
// Call the C function
|
||||||
|
for (int symbol = 0; symbol < re_cnt; symbol++)
|
||||||
|
dataF_out_cm[ant][symbol] = nr_layer_precoder_cm(n_layers, symbol_size, dataF_in, ant, &pmi_pdu, symbol);
|
||||||
|
|
||||||
|
// Call the C function
|
||||||
|
nr_layer_precoder_simd(n_layers, symbol_size, dataF_in, ant, &pmi_pdu, 0, re_cnt, dataF_out_simd[ant]);
|
||||||
|
|
||||||
|
// Compare the result from both C function
|
||||||
|
for (int symbol = 0; symbol < re_cnt; symbol++) {
|
||||||
|
EXPECT_EQ(dataF_out_cm[ant][symbol].r, dataF_out_simd[ant][symbol].r)
|
||||||
|
<< " at [" << ant << "][" << symbol << "] got real part: " << dataF_out_cm[ant][symbol].r << " result " << dataF_out_simd[ant][symbol].r;
|
||||||
|
EXPECT_EQ(dataF_out_cm[ant][symbol].i, dataF_out_simd[ant][symbol].i)
|
||||||
|
<< " at [" << ant << "][" << symbol << "] got imag part: " << dataF_out_cm[ant][symbol].i << " result " << dataF_out_simd[ant][symbol].i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
// Initialize random seed
|
||||||
|
srand(time(0));
|
||||||
|
|
||||||
|
testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
||||||
@@ -163,7 +163,7 @@ extern "C" {
|
|||||||
__attribute__((always_inline)) inline c16_t c16conj(const c16_t a) {
|
__attribute__((always_inline)) inline c16_t c16conj(const c16_t a) {
|
||||||
return (c16_t) {
|
return (c16_t) {
|
||||||
.r = a.r,
|
.r = a.r,
|
||||||
.i = (int16_t)-a.i
|
.i = (int16_t)-a.i
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,13 +61,7 @@
|
|||||||
|
|
||||||
c16_t convert_precoder_weight(double complex c_in)
|
c16_t convert_precoder_weight(double complex c_in)
|
||||||
{
|
{
|
||||||
double cr = creal(c_in) * 32768 + 0.5;
|
return (c16_t) {.r = round(SHRT_MAX*creal(c_in)), .i = round(SHRT_MAX*cimag(c_in))};
|
||||||
if (cr < 0)
|
|
||||||
cr -= 1;
|
|
||||||
double ci = cimag(c_in) * 32768 + 0.5;
|
|
||||||
if (ci < 0)
|
|
||||||
ci -= 1;
|
|
||||||
return (c16_t) {.r = (short)cr, .i = (short)ci};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void get_K1_K2(int N1, int N2, int *K1, int *K2, int layers)
|
void get_K1_K2(int N1, int N2, int *K1, int *K2, int layers)
|
||||||
|
|||||||
Reference in New Issue
Block a user