
给片上内存加一道权限检查AI 辅助 AXI MPU 研发实践今年接了个挺细碎的活儿给一颗SoC里的片上内存——准确说是挂在一个SRAM控制器前面的 AX I slave 接口——加一道访问权限检查。需求来自安全团队的整改项系统里同时挂着好几个能主动发起读写的 masterCPU、DMA、视频编解码单元、两个调试口谁都能摸到同一块内存。这放在任何讲究隔离的场景下都说不过去放在要过功能安全认证的项目里更是没法交代。以前做这类东西流程基本是先啃一遍 AXI 协议再抄一份别人家的 MPU 规格然后手写 RTL、手写断言、手动配寄存器、跑好几天回归。这次不太一样我从一开始就把 AI 工具拉进了流程里让它帮我生成寄存器描述、搭 RTL 骨架、补断言、解析仿真日志。整体下来效率比预想的高但坑也比预想的多。这篇文章把整个AI 辅助 AXI MPU 研发的过程拆开讲清楚重点覆盖权限检查机制的设计权衡、AI 生成代码的审查要点、验证阶段的边界打击以及几个能直接抄走的经验适合正在做 SoC 安全模块、总线互联或者对内存访问控制感兴趣的同行参考。1. 为什么非要在片上内存前面立一道门禁问题的真实来源1.1 没有 MPU 的 SoC内存访问有多裸奔系统里面的总线访问情况是这样的CPU 访问内存走 AXIDMA 也走 AXINPU 和 ISP 这些加速器也挂在互联上。问题在于AXI 协议本身只定义了 怎么传地址、怎么传数据从来不会定义谁有资格访问哪个地址。只要地址对得上、握手成功任何一个 master 都可以把数据写进任何一块内存也可以从任何一块内存读出数据。我这次真正踩到痛点是在一次集成测试里某个外设的地址生成逻辑出了个 off-by-one 错误直接把一段不该写的配置寄存器窗口给覆盖了现场表现为画面花屏加系统复位。查到最后根因不是这个外设坏了而是它的访问根本没有受限——它一开始就不应该具备对那块寄存器的写权限。这种问题放在验证阶段可能只是改一行代码放在量产设备上就是安全事故。所以 MPU 要做的事情非常朴素它位于总线和内存资源之间对每一个访问请求做地址范围 权限属性的核查不在白名单里的访问直接拦下返回错误响应。这个模块本身不产生任何业务数据它存在的唯一意义就是让那些不该发生的访问尽早暴露、立即失效。1.2 AXI MPU 和 ARM CPU 里的 MPU/MMU 不是一回事这里有个特别容易被混淆的点ARM Core 里面的 MPU/MMU 是给 CPU 核做虚拟地址翻译和缓存管理用的挂在 CPU 的 memory interface 上管的是CPU 自己能看到什么而 AXI MPU 是一个独立的硬件模块挂在总线的从端口前面面向的是总线上所有 master包括 CPU、DMA、调试器、加速器等等。说白了CPU 侧的 MPU 管你的代码不能越界AXI MPU 管谁的请求都不能越界。它不关心地址怎么翻译映射只关心这个物理地址窗口允许谁访问、以什么权限访问。我这次实现里是把它做成一个可以独立配置的从端部件插到 SRAM 控制器前面AXI slave 接口进、AXI slave 接口出对请求方完全透明需要保护时开启调试时也能关掉。这种透明插入的方式对上层软件很友好不需要改任何驱动代码只要在启动阶段完成 MPU 配置即可。1.3 需求清单动手之前先对齐安全基线动手之前我跟安全团队把需求对齐成几条硬性标准后面所有设计都围绕这几条展开。它们不单单适用于这个项目你去做类似设计也能直接拿来当 checklist内存区域至少划分成多个保护窗口每个窗口单独配置读写权限不同 master 按身份区分权限至少区分安全/非安全、特权/非特权违规访问不能静默吞掉必须对外返回错误响应同时留一个可观测的状态位方便软件诊断配置本身要防止被非特权软件篡改即配置寄存器的写访问也要受控burst 事务跨窗口边界时必须整体判定不能只查首地址前三项是安全模块的底线第四项是我这次特别强调的配置自身安全第五项是 AXI 事务里最容易被忽视的边界问题。后面所有 RTL 实现、验证用例的设计都围绕这五条展开。2. 定方案阶段的关键决策区域怎么配违规怎么响应2.1 保护区域地址窗口、使能位和优先级MPU 最核心的资源就是一组区域寄存器。我这次做的是 8 个区域窗口region每个窗口由四件事决定基地址BASE窗口的起始物理地址上限地址LIMIT窗口的结束物理地址使能位EN这个窗口是否参与本次判断权限掩码PERM读允许、写允许以及是否要求特权访问8 个窗口对片上 SRAM 这种资源来说足够覆盖典型场景比如安全固件区、普通数据区、共享 buffer 区、外设寄存器区。但窗口一多就带出一个问题区域重叠时听谁的实际固件配置时完全可能把一个大区和一个小区叠在一起小区表示在这个子区间里权限更严。我的处理办法是窗口编号带优先级编号小的优先命中配置软件只要遵守更严格的小区用更小编号的约定就不会产生歧义。这个决策听起来简单但它直接影响比较器 RTL 的匹配树结构优先级做错会留下安全漏洞。2.2 AXI 通道上到底在检查什么信号AXI 事务分读和写。读事务走 AR 通道发起之后在 R 通道拿数据写事务走 AW 通道发起然后 W 通道送数据、B 通道拿响应。权限判断应该发生在请求刚进来的时候也就是 AR 通道的地址、AW 通道的地址结合事务类型去和区域表做匹配。等到数据通道再判断就晚了那意味着无效数据已经进入了总线。这里要注意AXI 协议本身提供了一些现成的信号可以用来做权限判断。一个是 ARPROT/AWPROT里面带了 normal/privileged、secure/nonsecure、data/instruction 的编码另一个是 ARUSER/AWUSER属于用户自定义信号很多 SoC 用它来携带 master 的 ID 或安全属性。我这次两种方式都支持优先根据 master 的 ID 去查静态配置表这个相对可靠不容易被上游伪造PROT 信号作为一个可选过滤条件。如果你在设计里完全依赖 PROT要特别小心——有些 master 输出 PROT 时会一直拉低相当于永远以最宽松权限上报权限检查形同虚设。2.3 burst 事务跨窗口边界最容易漏掉的坑只检查事务首地址是远远不够的。AXI 的一次 burst 可能包含多个 beat地址从 start_addr 开始按增量方式一直往后走。如果一个 burst 起始地址落在窗口 A 内但 burst 长度加上地址增量冲进了窗口 B而窗口 B 不允许当前 master 写这个事务到底算合法还是非法正确的算法是把 burst 的终止地址也算出来要求整个地址区间完整落在某个允许的窗口内。具体到 RTL 公式就是start_addr和start_addr (len 1) * size - 1都要满足落在同一个命中窗口里。我见过不少第一版 MPU 只看了首地址结果等于给恶意 master 留了一条越过边界的通道它只要保证第一个 beat 在合法窗口内后面所有 beat 都可以自由写不该写的地址。这个问题必须摆在仿真用例的最前面专门构造首地址合法、尾地址越界的 case 去反复打击。2.4 违规响应SLVERR、读数据清零和状态上报当 MPU 判定某个请求违规时不能只是悄悄不放行否则发起方的协议状态机会一直等响应然后整条总线死锁。标准做法是把这个事务以错误响应结束掉读事务在 R 通道返回 SLVERR数据给全 0最后一拍带 RLAST保证读通道的握手节奏完整写事务W 通道正常收完所有数据然后在 B 通道返回 SLVERR除了错误响应我另外加了两个观测点一个是违规计数器和最近一次违规的地址/ID 快照软件可以通过寄存器读取调试时特别有用另一个是中断输出可以配置为每次违规立即上报验证阶段能通过中断尽早捕获问题。做安全审计时这两样基本是标配。2.5 配置寄存器的自我保护最后补一个容易漏的设计点MPU 的配置寄存器自己也要被保护。如果不加保护攻击者或者跑飞的非特权代码可以直接把 MPU 的窗口全部关掉那这道门禁就等于不存在。我的做法是给配置接口加一个独立的访问门控信号只有特权 master 发出的请求才能写入 BASE、LIMIT、PERM 这些关键寄存器非特权写请求直接返回 SLVERR。这个设计在需求清单里排第四条但实现优先级我拉到了最高——因为前面三条都依赖配置的正确性。3. AI 真正帮忙的地方从 RTL 骨架到寄存器模型的效率提升3.1 给 AI 下的第一道指令把规格文档翻译成可实现的接口定义很多人用 AI 写 RTL 是直接丢一句写一个 AXI MPU出来的东西大概率有格式问题更谈不上安全语义。我的做法是先把前面的需求清单翻译成一份伪代码级别的接口规格再让 AI 帮我补齐边界行为和配置项的默认值。比如我会在提示词里明确告诉它输入输出端口有哪些AXI 五个通道AW、W、B、AR、R的信号名和位宽区域寄存器的字段布局每个 BASE、LIMIT、PERM 的位宽和偏移比较逻辑的最小编号优先burst 越界判定公式违规时 R 通道/B 通道的时序行为把这种信息喂进去之后AI 生成的 SystemVerilog 骨架比我预想的要接近可用状态端口声明、寄存器读回逻辑、大部分比较运算都是对的。这不奇怪MPU 这类模块在开源社区和 IP 仓库里有大量相似实现大模型见过很多它最擅长的就是把已经存在过的模式按规格约束重新写出来。我把它生成的版本叫可评审的初稿而不是可交付的 RTL。3.2 审查 AI 生成的 RTL最值得盯的几个点AI 生成的代码能不能用关键看审查。我这次重点查了三个地方也是大家拿到 AI 代码后最该养成习惯去查的三类问题第一比较器的边界条件。AI 很喜欢写if (addr base addr limit)这种闭区间判断但不少总线窗口和内存控制器用的是左闭右开也就是应该用addr limit判断结束。这个差异放在功能上就是临界地址算不算溢出的一拍之差放在安全场景下就是越权访问的漏洞。第二数据通道的时序行为。AI 默认会把 R 通道写得非常规整比如违规时立刻拉低 RVALID但真实的 AXI 时序里RVALID 和 RLAST 要在握手节奏下配合违规处理不能破坏已经进行了一半的事务。AI 生成的代码读起来逻辑正确时序上却很可能把 valid 拉掉导致从端卡死这个只能靠仿真和断言去抓。第三寄存器的读写保护。AI 生成的寄存器默认软件能写就都能写但安全模块里配置寄存器本身的写访问必须受控。你要在提示词里明确写PERM 等关键寄存器只允许特权写否则 AI 不会主动设计这层逻辑。审查完之后我一般会拿掉 AI 写的注释按团队风格重写一版保留它的结构骨架。这个过程大概占据整个 RTL 工作量的一半但比从零开始写还是明显快。3.3 寄存器模型的 AI 辅助生成从 SystemRDL 到 UVM RegModelRTL 之外还有一堆和地址空间打交道的衍生物要写寄存器描述文件、UVM RegModel 的类、C 头文件里的寄存器偏移宏。这些是芯片开发里典型的重复劳动以前靠手工一个个敲excel 里抄来抄去特别容易错位。这次我把 SystemRDL 描述交给 AI 去生成同时给它地址 map 和字段列表。AI 生成的 RDL 文件质量相当高几乎不用改再从这个 RDL 派生 UVM RegModel 和 C 头文件基本就是传统跑脚本的流程。实测下来这个环节的省时程度比 RTL 本身还明显因为寄存器文件这个东西格式高度统一、例子极多、容错率要求高正好是大模型最舒服的领域。唯一要注意的是生成的注释不要直接进交付文档自己重新写一遍表达否则 review 的时候同事一眼就能看出是 AI 生成的套话。4. 怎么证明这道门真的把住了验证与调试实践4.1 必写的 SVA 断言违规时绝不静默MPU 这种安全模块光靠定向测试远远不够必须引入断言把不可接受的行为固化下来。我挑了几个最关键的断言场景给 AI 提示词时直接要求它生成可仿真的断言ar_addr_hit_region任何 AR 通道请求的地址只要不落在任何使能窗口内就必须在 R 通道最终响应 SLVERRaw_write_deniedAW 通道请求属于写禁止窗口时W 通道数据必须被丢弃B 通道返回 SLVERRcross_region_burstburst 的终止地址越界时整个事务按违规处理no_silent_drop从端不能在无响应的情况下吞掉一个请求所有进来的 AR/AW 都必须有对应的 R/B 响应这些断言配合一个可以随机注入违规模式的激励环境能很快把 RTL 的漏洞暴露出来。我在 AI 辅助下生成这些断言的初始版本只花了一天但后续调试定位的时间并没有被省掉——断言能告诉你行为不对不会告诉你哪一行 RTL 有问题。定位还是要靠波形。4.2 约束随机与错误注入把边界条件打穿验证里最有价值的场景是构造刚好卡在边界上的事务。我让验证工程师在 Sequence 里加了这样一组约束随机场景master 的起始地址随机落在某个窗口内burst 长度随机取 1 到最大支持长度然后事后检查整个地址区间是否越界。这个场景跑了几千轮果然抓出了我在 3.2 里提到的那个和的临界问题——首地址在窗口内、尾地址刚好压线行为就由那一行比较符号决定了。错误注入也必须覆盖到位。MPU 设计里会留一个调试模式可以临时放行某些违规请求方便软件调试。验证的时候我专门去测调试模式下确实能绕过、退出调试模式后恢复拦截这两个方向。同时还要模拟上游 master 发来非法 PROT 信号、非法 USER ID 的情况确保 MPU 不能因为上游撒谎而放行——权限判断必须以配置表里的静态映射为准协议信号只能作为附加条件。这些 case 单独列在回归列表里任何一次综合或后端改动后都要重跑。4.3 AI 辅助解析日志不能替代波形但能省出定位时间仿真跑完后能拿到大量日志和覆盖率报告手动翻浪费时间。我试过把一段失败用例的 log 直接交给 AI让它总结出请求地址、命中窗口、权限配置三者之间的矛盾点。它做得确实不错能很快整理出本次违规请求的 ID0x2地址 0xA1008 号窗口未使能匹配窗口为 2 号且禁止写这种可读结论省掉了挨个寄存器翻查的过程。但要泼一盆冷水AI 总结出来的结论只能当线索不能当证据。它的输出偶尔会把同一行日志误解成相反含义甚至因为提示词不够精确把允许和拒绝搞反。最后必须用波形去背书。我的习惯是让 AI 先汇总我再从汇总结果里挑可疑的十几行去波形里核对效率比纯手动高但绝不完全盲信。这也是整个项目里我对 AI 使用边界体会最深的一点。5. 时序、面积和几个能直接抄走的经验5.1 实测数据8 区域配置下代价有多大MPU 终究是插在关键路径上的一层逻辑面积和时序代价绕不开。我在 55nm 工艺库、200MHz 总线下做了评估8 个区域、单从端口的设计综合面积大约在几千个门级单元量级关键路径的额外延迟大约 1~2ns对大多数总线频率来说完全可以接受。但如果窗口数量加大到 32 个以上比较器和优先级 mux 树的延迟会明显上升届时要考虑对地址做粗粒度预解码把比较器分组或者干脆改用基于 content-addressable memory 的表项查找。做 MPU 之前先把窗口数量想清楚别等 RTL 写完了再翻工。5.2 和系统里其他安全机制的配合MPU 不是孤岛。如果 SoC 里已经有 TrustZone 这类全局安全架构AXI MPU 通常是安全总线策略的一部分一般放置在 memory controller 端口负责的是基于区域的保护而 TrustZone 负责的是基于安全状态的全局隔离两者可以叠用但配置必须协调一致。我这次实际碰到的问题是TrustZone 把某个区域标成了 SecureMPU 里却是普通权限窗口导致安全世界里的 master 访问被 MPU 错误拦掉。解决方法是让安全侧软件启动时统一初始化 MPU 配置表并且把 MPU 的配置寄存器的写权限纳入安全管控。这类协同问题规划得越早越好不然软件适配阶段要花大量精力去排查谁把谁拦了。5.3 给 AI 辅助研发划三条红线最后聊聊这次用得最多也最有边界感的部分。AI 确实把 MPU 这类高度规范化的硬件模块的开发速度提上去了但有三条红线我建议所有同行都记一下第一安全模块的 RTL 禁止直接沿用 AI 输出的一版代码。哪怕它语法全对、仿真通过你也要做一轮完整的安全评审每个人工改过的地方必须能解释清楚为什么改AI 写的部分必须有 review 记录。安全审计关心的不是代码效率而是可追溯性。第二AI 生成内容要主动标注来源。我们团队的习惯是凡是 AI 直接生成的 RTL 段落、断言、文档都会在注释里标注 generated with AI assistance后面接 reviewer 的名字和日期。这个习惯在项目评审时帮了很大忙也避免同事之间互相猜这段代码是谁写的、为什么风格不一样。第三别让 AI 替你做架构决策。它适合在决策已经定好之后帮你写代码、整理文档、总结日志不适合在你纠结该用 8 个窗口还是 16 个窗口、违规时该返回 SLVERR 还是直接截断地址这种问题时给你答案因为架构选择的依据是你这个项目的安全基线和总线拓扑AI 对你的完整上下文一无所知。它给的方案可能就是网上最常见的那个而最常见不等于最适合你正在做的这颗 SoC。这次把 AXI MPU 从规格一路推到验证收敛我最大的收获不是某一段代码而是把AI 能做什么、不该做什么的边界摸清楚了。下一颗芯片如果再挂类似的防护需求我可以把这套流程直接固化复用先定安全清单再让 AI 铺骨架人盯住边界条件和时序行为然后把断言和错误注入做扎实最后用 AI 加速日志排查、用波形守住底线。给片上内存加一道权限门这件事本身不复杂复杂的是让这道门在每一个边界情况下都可靠地关上。