linux OCUDU 项目介绍-12(PHY层数字信号处理库介绍--7--预编码)

1.PDSCH 处理流程:

PDSCH

2.预编码:

预编码就是图中的第3步,代码的实现

2.1 涉及的文件清单

文件 角色
channel_precoder_impl.h/cpp 抽象基类,定义模板方法
channel_precoder_generic.h/cpp 通用标量实现
channel_precoder_avx2.h/cpp x86 AVX2 实现
channel_precoder_avx512.h/cpp x86 AVX512 实现
channel_precoder_neon.h/cpp ARM NEON 实现
precoding_factories.cpp 工厂,按 CPU 特性选择实现
transform_precoder_dft_impl.h/cpp transform precoding(DFT 预编码),另一条链路
transform_precoding_factories.cpp transform precoder 工厂
dft_processor_*.h/cpp 被 transform precoder 使用的 DFT 处理器

2.2 类层次结构:

precode

2.3 channel_precoder_impl:模板方法

头文件只暴露一个纯虚函数:

virtual void apply_precoding_port(
    span<cbf16_t>             port_re,
    const re_buffer_reader<>& input_re,
    span<const cf_t>          port_weights) const = 0;

apply_precoding 是公有的非虚函数,做统一的参数检查,然后逐端口调用虚函数:

void channel_precoder_impl::apply_precoding(
    re_buffer_writer<cbf16_t>&     output,
    const re_buffer_reader<>&      input,
    const precoding_weight_matrix& precoding) const
{
  // 5 条断言:RE 数一致、端口数 ≥ 层数、端口/层数与矩阵匹配
  for (unsigned i_port = 0; i_port != nof_tx_ports; ++i_port) {
    span<cbf16_t>    port_re_view     = output.get_slice(i_port);
    span<const cf_t> port_weights_view = precoding.get_port_coefficients(i_port);
    apply_precoding_port(port_re_view, input, port_weights_view);
  }
}

这是典型的模板方法模式:循环骨架在基类,逐端口算法交给子类,apply_layer_map_and_precoding 是另一个虚函数,用于 ci8_t 输入的场景(层映射 + 预编码一步完成),基类里不实现。

2.4 channel_precoder_generic:标量参考实现

apply_precoding_port(cf_t 输入 → cbf16_t 输出):

for (unsigned i_re = 0; i_re != nof_re; ++i_re) {
  cf_t sum = layer_re_view_list[0][i_re] * port_weights[0];
  for (unsigned i_layer = 1; i_layer != nof_layers; ++i_layer) {
    sum += layer_re_view_list[i_layer][i_re] * port_weights[i_layer];
  }

  port_re[i_re] = sum;
}

就是逐 RE、逐层做 y = Σ w_l · x_l,没有 SIMD,纯粹作正确性参考。

apply_layer_map_and_precoding(ci8_t 输入):

for (unsigned i_re = 0; i_re != nof_re; ++i_re) {
  for (unsigned i_port = 0; i_port != nof_ports; ++i_port) {
    span<const cf_t> port_weights = precoding.get_port_coefficients(i_port);
    span<cbf16_t>    port_re      = output.get_slice(i_port);
    cf_t sum = to_cf(input[nof_layers * i_re]) * port_weights[0];
    for (unsigned i_layer = 1; i_layer != nof_layers; ++i_layer) {
      sum += to_cf(input[nof_layers * i_re + i_layer]) * port_weights[i_layer];
    }
    port_re[i_re] = sum;
  }
}

2.5 precoding_factories.cpp:工厂选择

if (((precoder_type == "avx512") || (precoder_type == "auto")) && supports_avx512) {
  return std::make_unique<channel_precoder_avx512>();
}
if (((precoder_type == "avx2")   || (precoder_type == "auto")) && supports_avx2 && supports_fma) {
  return std::make_unique<channel_precoder_avx2>();
}
// NEON 分支在 __ARM_NEON 下
if ((precoder_type == "generic") || (precoder_type == "auto")) {
  return std::make_unique<channel_precoder_generic>();
}
return nullptr;

3.transform_precoder_dft_impl:另一条链路

这就是前面讨论过的 DFT 预编码,跟 MIMO 预编码不是一回事

void deprecode_ofdm_symbol(span<cf_t> x, span<const cf_t> y)
{
  unsigned M_sc = x.size();
  unsigned M_rb = M_sc / NOF_SUBCARRIERS_PER_RB;
  // 断言:size 一致、能被 12 整除、PRB 数合法、有对应 DFT 处理器
  float scaling_factor = 1.0F / std::sqrt(static_cast<float>(M_sc));
  dft_processor& dft = *dft_processors.at(M_rb);
  ocuduvec::copy(dft.get_input(), y);
  span<const cf_t> out = dft.run();
  ocuduvec::sc_prod(x, out, scaling_factor);
}

4.两条链路的对比

项目 channel_precoder_* transform_precoder_dft_impl
作用 层 → 天线端口,空间预编码 符号 → 子载波,DFT 扩频
数学 矩阵乘 y = W·x DFT/IDFT
输入输出维度 层数 → 端口数 时域 → 频域(或反向)
方向 发送端预编码 接收端解预编码(direction::INVERSE)
底层依赖 SIMD(AVX2/512/NEON) dft_processor(generic/FFTW/FFTZ)
工厂 create_channel_precoder_factory create_dft_transform_precoder_factory
两者在 5G NR 里是可以同时存在的:PUSCH 开启 transform precoder 时,顺序是「层映射 → transform precoding → MIMO precoding」。

5.设计模式总结

模式 体现
模板方法 apply_precoding 定骨架,apply_precoding_port 交给子类
策略模式 generic / avx2 / avx512 / neon 可互换
工厂方法 precoding_factories.cpp 按 CPU 特性选实现
抽象接口 channel_precoder 对外,channel_precoder_impl 对内
条件编译 x86_64 / __ARM_NEON / AVX512BF16 分开编译
posted @ 2026-09-10 11:18  Robertlin_Martin  阅读(3)  评论(0)    收藏  举报