linux OCUDU 项目介绍-12(PHY层数字信号处理库介绍--7--预编码)
1.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 类层次结构:

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 分开编译 |

浙公网安备 33010602011771号