资讯动态

5G小基站PHY SPI接口:MAC与PHY对话规则与实战避坑

发布时间:2026/10/20 18:10:16 来源:尧图企业网站定制
简介SCF222_5G-FAPI_PHY_SPI_Specification.pdf 是Small Cell Forum正式发布的5G小基站物理层接口规范主要面向小基站基带算法开发、协议栈集成、射频前传联调及通信标准研究的工程师旨在统一FAPI前传应用编程接口PHY层在SPI规范下的接口定义、消息交互与参数配置流程解决不同厂商基带与射频单元之间集成适配难、互联互通成本高的问题。这份规范为单文件PDF文档压缩包整体约2.36MB对应Small Cell Forum Release 10.02020年3月版本内容覆盖FAPI接口的功能模型、消息交互流程、参数配置结构以及5G NR物理通道相关定义既可作为软件架构设计时的接口参考也可作为驱动开发、联调测试和问题定位的权威依据。已有1438人学习下载。文档源自SCF正式发布程序适合5G小基站产品研发、FAPI接口开发、PHY层协议测试等岗位的工程师系统研读对理解小基站开放接口体系、降低多厂商设备集成门槛、提升产品互操作能力具有直接的工程参考价值。1. 拿到 SCF222 PDF 之后5G 小基站 PHY SPI 到底在解决什么某开发者第一次接触 5G 小基站协议栈时被 PHY 厂商的对接文档折腾了整整一周MAC 层明明按时把下行调度发下去了PHY 侧却总是说“时隙对不上”最后才发现双方对“提前几个时隙下发”的约定完全不一样。SCF222_5G-FAPI_PHY_SPI_Specification.pdf 这份规范解决的就是这类问题——它定义了 5G 小基站里 MAC 层与 PHY 层之间的接口消息集合把小区配置、时隙调度、上行反馈、测量上报这些交互方式固化成一套标准“对话协议”。理解它之后无论你的 PHY 是自研代码、第三方 IP 还是黑盒加速器上层逻辑都能按同一套思路对接不用再猜对方怎么想。适合读这篇文章的人是做协议栈、基带/DSP、仿真验证的工程师以及刚进入小基站方向想搞清楚 L2 与 PHY 边界的新人。2. PHY SPI 的角色定位不写物理层算法只写 MAC 和 PHY 的“对话规则”2.1 在 FAPI 接口族里PHY SPI 站在哪一层小基站标准化组织定义的 FAPI 并不是一个单一接口而是一族接口的统称。按层次拆分大致有P1 负责核心网与高层之间P2 是 PHY 往上层方向的上报通道P3 是上层往 PHY 方向的下发通道P4 是 PHY 内部多个处理节点之间的连接P5 则是 PHY 与射频前端之间的采样接口。SCF222 对应的 PHY SPI是 5G NR 时代把 P2/P3 职责整合后的完整定义它关心的粒度是时隙和符号而不是 IQ 采样点。很多人容易把 PHY SPI 和 P5 搞混。P5 上跑的是采样率和 IQ 数据解决的是“射频怎么把信号交给基带”而 PHY SPI 上跑的是调度指令和结果上报解决的是“MAC 怎么指挥 PHY 在哪个时隙做什么”。一个在数字前端一个在协议栈内部层次完全不同。做集成时如果拿 P5 的思路去理解 PHY SPI很容易在消息时序上翻车。为什么需要这样一层标准化接口因为小基站产品里 MAC 和 PHY 往往不是同一个团队交付的甚至 PHY 是外购的黑盒。如果没有统一接口交接边界就模糊MAC 侧不知道 PHY 需要多长的处理时间PHY 侧不知道 MAC 什么时候给调度最合理。PHY SPI 存在的意义就是把所有交互都收敛成显式的消息双方只需要保证“消息格式一致、时序符合规范”就能完成集成。2.2 三类消息配置、控制、数据从用途上区分PHY SPI 消息大体分成三类。配置类消息负责一次性或低频的参数设置比如小区级参数、载波参数、波束配置通常在小区建立阶段下发之后很少变动。控制类消息负责每个时隙的调度与反馈下行有调度请求、上行有调度请求、时隙指示、CRC 结果等这类消息频率最高是整个接口的主体。数据类消息负责承载传输块、HARQ 相关的数据内容往往与控制消息成对出现。工程实现上常见做法是定义统一的消息结构体用消息 ID 区分类型用环形缓冲或信号量做传输。下面是一个简化到可写工程代码的结构typedef struct { uint32_t msg_id; /* 消息类型如 DL_TTI_REQ / UL_TTI_REQ */ uint16_t sfn; /* 系统帧号0~1023 */ uint16_t slot; /* 时隙号由子载波间隔决定范围 */ uint32_t flags; /* 保留位或错误标志 */ union { config_req_t config; /* 配置类消息体 */ dl_tti_req_t dl_tti; /* 下行调度消息体 */ ul_tti_req_t ul_tti; /* 上行调度消息体 */ } body; } fapi_msg_t;这段代码的逻辑是MAC 侧填充sfn/slot和对应消息体通过接口发给 PHYPHY 侧拿到消息后先看msg_id再决定走哪条处理分支。这里sfn/slot是最关键的两个字段PHY 必须依据它们做时分对齐否则整个调度节奏都会乱掉。参数说明sfn取值范围与 LTE/NR 一样是 0~1023slot的范围取决于子载波间隔30 kHz 下每帧 20 个时隙取值范围就是 0~19。msg_id的枚举值必须与规范中的编号保持一致不同版本之间可能会有差异建议做成独立的头文件统一管理不要散落在代码里。实际对接中最常见的低级错误就是msg_id枚举值两边各写了一份结果消息到了 PHY 侧全被当成未知类型丢弃。这类问题排错时很难一眼看出来因为链路本身是通的只是消息全被“吞”了。2.3 PHY SPI 标准化带来的三个实际收益第一个收益是可替换性。MAC 侧按标准消息驱动 PHY那么换一家 PHY 供应商时改动被限制在适配层调度器逻辑基本不用动。第二个收益是可仿真性。在没有真实基带硬件时可以把 PHY 替换成一个软件模型模型只负责按规范回消息MAC 侧代码就能在 x86 环境跑起来做功能验证。第三个收益是可测试性。因为消息格式统一接口上的日志可以规格化打印对照规范就能判断实现是否符合预期联调效率比私有接口高很多。当然标准化也有代价。接口本身有封装损耗每条消息都要带头部和校验信息对实时性要求极高的场景需要仔细设计传输机制。但从产品集成角度看这点损耗换来的边界清晰非常值得。如果你正在做的是私有 PHY 和自己写的 MAC或许会觉得这套接口是多余的一旦加入第三方组件或需要外场联调就会理解为什么要提前按规范约束好。3. 跑通 PHY SPI 的最小流程从小区配置到逐时隙调度3.1 开机三步能力查询、小区配置、时隙指示一个小区从启动到正常调度PHY SPI 上至少要经历三步。第一步是能力查询MAC 发参数请求PHY 回能力参数告诉上层自己支持哪些 numerology、最大带宽、处理延迟等。第二步是小区配置MAC 下发完整的小区级配置PHY 配置成功后回复确认双方进入“已配置”状态。第三步才是时隙级的调度交互PHY 在每个时隙产生指示MAC 根据指示下发对应的调度请求。常见做法是能力查询只在启动时做一次结果缓存在 MAC 侧小区配置在建立小区时做而时隙指示和调度请求则是持续运行的核心循环。小区配置这一步特别值得注意PHY 返回的确认里往往带有处理延迟参数这个值要保存下来并传给调度器后面所有时隙的提前量都靠它计算。如果这里漏了后续调度时序大概率对不齐。提示能力查询和小区配置阶段的消息都是低频的排错相对容易真正难查的时序问题几乎都出在第三步也就是时隙级调度阶段。3.2 下行调度闭环DL_TTI.request 与 CRC.indication下行调度是一个典型的请求-反馈闭环。MAC 提前若干时隙把下行调度请求发给 PHYPHY 在指定时隙把 PDSCH/PDCCH 发下去完成空口发送后再做 CRC 校验最后通过 CRC 指示消息把结果上报给 MAC。MAC 根据这个结果决定是新传还是重传。过程中的关键节点可以用表格概括阶段消息方向关键内容调度准备下行调度请求MAC → PHY时隙号、PDSCH 资源、调制编码方式空口发送下行链路PHY → UE传输块、HARQ 进程号、冗余版本结果上报CRC 指示PHY → MAC时隙号、HARQ 进程号、CRC 结果MAC 侧需要一个状态机来跟踪每个 HARQ 进程。下发调度请求时记录发送状态收到 CRC 指示后更新进程状态如果是 NACK 则安排重传。这里很容易忽略的一点是CRC 指示消息里的时隙号必须与调度请求里的一致不能拿当前时隙去判断否则重传逻辑会出错。代码上可以用这样的结构管理typedef struct { uint16_t sfn; uint16_t slot; uint8_t harq_id; uint8_t status; /* 0空闲, 1已调度, 2已反馈 */ } harq_proc_t; harq_proc_t proc[16]; void on_crc_ind(uint16_t sfn, uint16_t slot, uint8_t harq_id, uint8_t crc_ok) { proc[harq_id].status 2; if (!crc_ok) { /* 安排重传重传时仍使用原 sfn/slot 对应的资源 */ } }这个片段展示的是 HARQ 进程收到反馈后的处理逻辑先标记进程状态为已反馈再根据 CRC 结果决定是否重传。关键点在注释里重传时要回溯到原始调度的时隙号而不是用当前时隙因为 PHY 上报时携带的时隙信息才是那次空口传输的真实位置。参数说明harq_id是上下行各自独立的进程号下行 HARQ 进程数一般 8 到 16 个取决于 UE 能力和基站配置status这个状态位是避免重复反馈覆盖前一次结果的简单手段实际工程里还要加超时保护。3.3 上行调度闭环UL_TTI.request、UCI.indication 与数据接收上行调度与下行不同它先由 MAC 发送上行调度请求配置 PHY 在指定时隙做接收然后 UE 在空口发送PHY 解调后把数据以及测量结果一起上报。这里的反馈通道主要是 UCI 指示消息里面携带 HARQ-ACK、CSI、SR 等信息MAC 拿到后更新链路自适应参数。上行调度的时序更敏感因为 UE 的发送受 TA时间提前控制MAC 必须把 TA 值透传给 PHY。如果 TA 换算错误PHY 接收窗和实际到达信号对不齐解调性能会明显下降。这个现象在室内小基站场景特别明显小区覆盖小、信号强CRC 却频繁出错查来查去最后发现是 TA 参数量纲没对齐。上行数据接收还有一个细节PHY 解调出传输块后可能需要通过数据消息把完整的数据内容交给 MAC而不是像下行那样只上报 CRC。因此在实现上上行链路除了处理 UCI 指示还要维护数据缓冲区的生命周期防止 PHY 写入的数据在 MAC 还没来得及读取时被覆盖。3.4 SLOT 指示为什么不能省时隙对齐是调度器命门PHY 在每个时隙开始时会产生 SLOT 指示消息本质上是告诉 MAC“新的时隙开始了”。很多 MAC 实现会用这条消息作为调度器的时间基准在指示到达后触发下一轮调度计算。也有实现不用它完全靠内部定时器驱动但这要求两个模块的时钟严格同步工程上容易出偏差。有了 SLOT 指示至少可以在 MAC 侧做一层保护收到指示时校验当前期望的时隙号发现不连续就打印告警。这种校验成本很低但对定位时隙错位极有帮助。比如下面的判断逻辑static int slot_expect(uint16_t cur_sfn, uint16_t cur_slot, uint16_t *next_slot) { if (cur_slot 1 slots_per_frame) { *next_slot 0; return (cur_sfn 1) 0x3FF; /* sfn 回绕处理 */ } *next_slot cur_slot 1; return cur_sfn; }这段代码解决的是 sfn/slot 递推中的边界问题时隙号到帧尾时需要进位系统帧号到 1023 后要回绕到 0。很多对接问题恰恰发生在 sfn 回绕的那一刻——MAC 侧还在用 1023 的帧号PHY 侧已经切到 0两边差了一整帧。参数说明slots_per_frame由子载波间隔决定30 kHz 时为 20 0x3FF是因为 NR 系统帧号就是 10 bit。这个函数建议放在通用工具模块里MAC 和 PHY 复用同一份实现避免各写一遍导致规则不一致。4. 关键参数从哪里来把规范字段映射到调度器的四个重点4.1 numerology、带宽与 pointA配置错一步接入就卡住PHY SPI 配置消息里第一组关键参数是载波相关的子载波间隔、带宽、pointA。pointA 是公共资源块的参考点所有资源分配都围绕它计算。配置错误时症状往往很隐蔽——基站自己觉得自己配置正常但 UE 侧无法完成初始接入或者接入后频域资源完全错位。常见做法是在 MAC 侧维护一个载波配置结构字段直接对应规范里的定义。以 30 kHz 子载波间隔、100 MHz 带宽为例carrier_cfg_t cfg { .scs 30, /* kHz对应 numerology 1 */ .bw 100, /* MHz决定 RB 总数 */ .pointA arfcn_from_ssb(ssb_freq), /* 由 SSB 频点查 ARFCN 表得到 */ .offset 0, /* pointA 与资源块的偏移 */ };逻辑说明arfcn_from_ssb是一个换算函数把 SSB 的绝对频点换算成规范的 ARFCN 数值不能直接把 MHz 频率填进去。这也是最常见的错误把 3.5 GHz 频点直接写成 3500000000 填进 pointAPHY 侧按照规范单位解析后完全混乱。参数说明scs决定时隙长度和每帧时隙数bw决定可用 RB 数100 MHz 在 30 kHz 子载波间隔下约 273 个 RBpointA的单位是 ARFCN和频点不是一个量纲。这部分务必在接口适配层做一次统一换算并加单元测试覆盖。4.2 时隙格式与 TDD 上下行比例调度方向必须和 PHY 一致5G 小基站绝大多数跑 TDD每个时隙是下行、上行还是保护间隔由时隙格式决定。PHY SPI 的下行调度请求和上行调度请求里都会带方向信息MAC 必须保证发出去的方向和 PHY 侧配置的时隙格式一致否则 PHY 可能直接丢弃消息。工程上常见做法是配置一张时隙格式表按每帧 20 个时隙循环使用。代码里可以这样组织/* 0DL, 1UL, 2GP按 DDDSU 比例配置一帧 */ static const uint8_t slot_fmt[20] { 0,0,0,0,1, 0,0,0,0,1, 0,0,0,0,1, 0,0,0,0,1 }; uint8_t get_slot_direction(uint16_t sfn, uint16_t slot) { return slot_fmt[slot % 20]; }逻辑说明MAC 调度器在每个时隙计算方向时直接查表拿到 DL/UL 指示然后决定发哪种调度请求。这张表必须和小区配置阶段下发给 PHY 的时隙配置完全一致否则会出现 MAC 发下行调度、PHY 认为该时隙是上行的情况。参数说明示例里用的是 4 下行 1 上行的常见室内配置实际比例根据业务模型调整关键是把表做成唯一的配置源MAC 和 PHY 适配层都从这个表取方向不要各维护一份。4.3 处理延迟与提前量PHY 不是即时执行的很多第一次对接的人默认 PHY 收到调度请求后能在同一时隙执行这是最大的误解。PHY 从收到消息到完成基带处理、形成发射信号需要固定的处理时间这个时间在 PHY SPI 里通过参数告诉 MAC。MAC 调度器必须“往前看 N 个时隙”计算第 K 个时隙的资源时提前 N 个时隙把调度请求发出去。N 的取值会直接影响空口性能。N 太小PHY 来不及准备时隙直接空发N 太大调度周期拉长链路自适应响应变慢。我一般会把这个值做成可配置项先按 PHY 供应商提供的典型值起步然后通过压力测试观察空口吞吐和误码率做微调。如果你拿到的 PHY 是黑盒可以做一个简单的试验把提前量从 2 逐步加到 8观察 CRC 错误率的变化曲线稳定且最低的点就是当前负载下的合理值。这里还要注意一个细节提前量不是所有消息都相同。小区配置等低频消息没有严格提前量要求但调度请求必须严格遵守。代码实现时建议把提前量作为调度器构造参数传入而不是到处硬编码。4.4 参数单位与换算symbol、RB、ARFCN 别再自己拍脑袋PHY SPI 对接中一半以上的参数错误都和单位有关。规范里的字段单位往往不是直觉上的 Hz、秒而是约定好的抽象单位比如频率用 ARFCN、时间用符号或时隙、资源用 RB。这一节值得单独梳理一张常用换算表参数规范字段常见单位易错点频点ARFCN直接用 Hz 填入会差几个数量级时隙提前量时隙或符号数混用这两者会导致提前量错一截资源分配RB 编号和 OFDM 子载波编号混用TA 时间提前符号或采样点直接从 RRC 层拿数值不换算就透传每遇到一个新字段先确认单位再写代码。实在不确定时可以查一下规范里的定义说明或者在适配层打日志对比 PHY 侧的解析结果。宁可多花十分钟确认单位也不要等上了外场再排查这种低级错误。5. 对接避坑PHY SPI 落地最容易踩的五个雷5.1 现象定时调度偶发丢失PHY 侧找不到目标时隙**现象**系统跑起来大部分时间正常但不定什么时候 PHY 侧日志会报“未找到对应时隙”该时隙的下行数据直接丢失重跑同样的调度序列又不一定能复现。**原因**MAC 侧提前量计算用的是本地时钟PHY 侧按自己的时隙计数对齐两边的 sfn/slot 不一致。最常见的是 sfn 回绕时 MAC 没有处理进位导致整帧错位。**解决**把 sfn/slot 递推逻辑收敛到同一个工具函数MAC 和 PHY 共用在接口日志里对每个调度请求打点记录 sfn、slot、消息时间戳对齐后就能看到错位发生在哪个边界。我接手过的项目里这一条占时隙类问题的一半以上。5.2 现象上行 CRC 频繁报错但下行链路正常**现象**下行吞吐正常上行吞吐上不去PHY 上报的 CRC 错误率异常高射频指标检查过是正常的UE 信号也够强。**原因**上行调度请求里的 TA 或时域资源位置配置错误PHY 的接收窗和实际信号到达时间对不齐。另一个常见原因是上行调度请求提前量没有单独配置直接沿用了下行的提前量。**解决**确认 TA 的单位和换算关系在适配层统一换算给上行调度单独配置提前量参数。排查时让 UE 静止在近点位置排除频偏和多径干扰集中验证 TA 字段。5.3 现象RACH 一直检测不到 preamble**现象**UE 在小区搜索阶段能读到 SSB但发随机接入前导后基站侧一直没有响应PHY 日志里看不到 RACH 检测结果。**原因**RACH 配置消息里的频域起始位置没有正确映射到 pointA或者 preamble 格式与小区配置不一致PHY 在错误的资源位置做检测。**解决**先在配置层做一致性检查确认 RACH 频域位置、preamble 格式、接收窗口三者匹配再抓 PHY 侧的检测日志确认检测窗是否落在预期时隙内。这种问题通常不会是射频原因别把时间浪费在天线和馈线上。5.4 现象改大带宽后速率不升反降**现象**从 20 MHz 带宽切到 100 MHz峰值吞吐反而下降PHY 侧频繁报资源分配出错。**原因**调度器还在按旧带宽的 RB 数做资源分配而 PDSCH 资源指示按新带宽的 RB 编号下发两边位图长度不匹配PHY 解析越界。**解决**带宽参数只影响载波配置调度器还必须同步更新自己的资源池信息包括 RB 总数、资源位图长度、PDCCH 可用资源。改配置后加断言调度的 RB 范围和带宽配置必须一致不一致直接报错而不是静默处理。5.5 现象消息内容偶发损坏解析结果前后不一致**现象**PHY SPI 消息在传输过程中极少出现乱码一旦出现PHY 侧解析出的参数明显不合理但传输层本身没有报错。**原因**消息结构体没有按紧凑方式定义发送方和接收方编译器对结构体做了不同对齐导致字段偏移不一致。这个坑在自己 GCC 版本不一致、或者一端是 DSP 编译器一端是 ARM 编译器时特别容易踩。**解决**结构体定义加紧凑对齐指令并在头文件里用静态断言检查结构体大小两端编译时一旦大小不一致立即报错。别只靠代码审查编译器差异造成的偏移问题是肉眼很难看出来的。6. 验证技巧用双端日志对齐 PHY SPI 时隙错位做 PHY SPI 联调时我最依赖的验证手段是“双端日志对齐”。原理不复杂MAC 侧和 PHY 侧各自在消息入口打印日志记录(sfn, slot, 消息名, 时间戳)跑完一段场景后把两份日志按(sfn, slot)做关联对比任何时隙层面的错位都会立刻现形。关键是日志里要有绝对参考点每个系统帧的第一个时隙打一条特殊标记比如FRAME_START这样两边日志对齐时有锚点不会因为只比较相对序号而错开一帧都发现不了。写一个简单的解析脚本就能做这件事import re mac_events {} for line in open(mac.log): m re.search(r\[(\d),(\d)\] (\w), line) if m: key (int(m.group(1)), int(m.group(2))) mac_events.setdefault(key, []).append(m.group(3)) for line in open(phy.log): m re.search(r\[(\d),(\d)\] (\w), line) if m: key (int(m.group(1)), int(m.group(2))) if key not in mac_events: print(fPHY-only event at {key}: {m.group(3)})逻辑说明这个脚本只做最基础的一步——把 PHY 侧出现的(sfn, slot)拿去向 MAC 侧查有没有对应记录如果查不到说明该时隙两边没有对上。实际使用时可以扩展成对同一时隙的所有消息做列表对比看顺序是否一致、有没有漏消息。参数说明日志正则里的(\w)抓消息名(\d),(\d)抓 sfn 和 slot如果日志时间戳包含毫秒还可以在对比时增加时间差统计超过阈值自动告警。把这个技巧和其他办法配合会更好用跑固定场景前先清空两边日志缓冲结束时先看FRAME_START的数量是否一致再逐帧对齐发现偏差后回到调度器检查提前量配置。我最早做 PHY SPI 对接时花了两天查一个偶发丢调度的问题最后确认是 sfn 回绕那一帧两边差了一位当时要是早点打FRAME_START标记半小时就能定位。后来每做一轮联调我都会先把这个日志链路铺好再开始跑场景这个习惯帮我少加了不少班希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑