ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于AXI的外设访问控制IP核:权限模型与原子更新设计

基于AXI的外设访问控制IP核:权限模型与原子更新设计 1. 为什么我会把外设访问控制做成一个IP核1.1 项目缘起一个尴尬的Demo现场做嵌入式SoC设计的人应该都有过这种经历功能仿真全过、时序约束全绿、上板自测正常结果一到客户现场或评审演示一个USB设备热插拔就把系统搞崩了。我在做一款工业控制SoC的验证时也遇到过类似问题——外设接口是开放的任意主机都可以通过USB或PCIe枚举到内部设备读走配置寄存器还是小事更麻烦的是有人通过调试接口直接下发非法命令把外设状态机打到不可恢复的模式。当时项目里正好要引入AI辅助设计流程我就想能不能用AI先把外设访问控制的逻辑框架搭出来再手工把关键路径打磨成可综合的RTL最后做成一个独立的IP核挂到总线上。于是就有了这个项目一个基于请求拦截和权限原子更新机制的外设访问控制IP。它做的事情一句话就能说清在每个外设请求进入总线互联之前插入一个检查节点判定这次访问是否被允许允许就放行不允许就返回错误响应同时把所有判定和更新操作做成原子事务避免权限检查与权限修改之间出现竞态。这篇文章我打算把整个设计过程拆开讲清楚包括AI辅助生成代码时哪些部分能复用、哪些部分必须人工重写请求拦截节点的总线协议适配怎么做以及权限表原子更新的设计细节。适合正在做SoC验证、安全IP设计或者想尝试AI辅助IC设计流程的工程师参考。1.2 设计目标和适用范围先说清楚这个IP的定位。它不是一个通用的防火墙也不是要替代外设本身的密钥认证逻辑它解决的是谁有权限在什么时间访问哪个外设寄存器这个访问控制问题。适用场景包括多主机共享外设的SoC比如USB主机控制器、以太网MAC、SD/eMMC控制器挂在同一套总线矩阵上需要限制非授权主机访问。车载或工控场景中的安全关键外设比如安全气囊控制器的配置寄存器、BMS电池管理系统的校准接口这些寄存器一旦被非预期写入可能造成严重后果。支持在线更新权限表的系统比如运行中加载新的安全策略、热切换外设归属权。需要特别说明的是我做的这个版本是面向AMBA AXI4总线的因为AXI4在SoC互联中太常见而且它的通道分离机制读写地址通道、读写数据通道分开天然适合插入拦截逻辑。如果是AHB或APB总线设计思路类似但事务原子性处理和响应返回方式会有差异后面我会提到。2. 权限模型怎么建模从粗粒度开关到原子表项2.1 先定访问粒度按地址区间还是按事务属性权限控制的第一件事是定义一次访问的最小单位。我见过不少设计把粒度定在某个外设能不能被访问这个级别也就是每个外设只有一个全局开关。这种方案的优点是逻辑简单缺点也明显——一旦放行就是整个地址空间放行外设里敏感的配置寄存器照样暴露。我的设计里把粒度拆成两级第一级是设备级使能对应外设基地址的地址区间这个区间通过地址解码器确定。第二级是事务级属性检查在设备级放行的基础上再根据请求的事务类型读还是写、burst长度、保护位prot[1]表示特权访问、以及具体命中的地址偏移决定是否允许这次请求。实际项目中外设地址空间往往被划分为控制寄存器区、状态寄存器区、数据缓冲区。控制寄存器区通常只允许特权写状态寄存器区只允许读数据缓冲区可以读写。这种粒度差异用单个开关根本表达不了所以我在IP里维护了一张权限表表的每一行对应一个地址区间和一个访问属性集合。2.2 权限表项设计还是用掩码比区间更省逻辑权限表项的字段定义如下字段宽度说明base_addr32区间起始地址range_mask32区间掩码1表示比较位read_en1是否允许读事务write_en1是否允许写事务secure_only1是否仅允许安全访问prot[1]1burst_limit3允许的最大burst长度0-7valid1表项是否有效用base_addr加range_mask的做法比[start, end]区间更省硬件地址匹配时只需一次位与运算不需要减法比较器。这个设计是我从MMU的地址翻译思路挪过来的实际综合下来比直接比较start和end减少了大约20%的LUT占用。表项数量我配置为8个这个数量覆盖了大多数场景每个表项宽度大约75位总存储不到600位用寄存器实现即可不需要RAM。但如果要支持更多外设或更细的权限策略可以把表项存到SRAM里只是要注意原子更新时的读写冲突。2.3 默认策略拒绝是一切安全设计的底线权限模型里最容易被忽略的是默认策略。我采用的默认策略是deny-all任何请求如果在权限表中找不到匹配表项直接拒绝。这与很多人的直觉相反——很多人倾向于默认放行、只拦黑名单但在外设访问控制这个场景下默认拒绝是唯一安全的选择。黑名单思路的问题在于你不知道未来会出现什么新的攻击模式也不知道外设寄存器配置变化后会有什么新的敏感面。白名单思路则把这个忧虑反转过来——只有明确允许的访问才能通过新增的访问模式都需要显式配置。这个原则我建议所有做安全IP的工程师都刻在脑子里默认拒绝显式放行。3. AI辅助设计过程哪些能交给大模型哪些必须自己写3.1 AI生成代码的实际用法和边界我这次用AI辅助RTL设计的流程坦白说不是那种输入需求直接输出完整IP的科幻用法。以现在的生成式大模型能力直接让它产出可综合、可验证、无时序毛刺的完整安全IP还不太现实。我的用法是分解任务让AI生成功能骨架比如请求解析逻辑的标准模板、AXI通道握手状态机的初版代码、寄存器配置界面的读写逻辑。让AI生成验证辅助代码比如寄存器模型的SystemVerilog类、接口的task封装这些代码模式固定AI生成后稍作修改就能用。让AI生成注释和文档框架省掉边写代码边补注释的时间。真正需要手写的是三块权限匹配的并行比较逻辑涉及多个表项同时比较时优先级仲裁、原子更新的乒乓缓冲控制、以及跨时钟域的同步处理。这三块对正确性要求极高AI生成代码的错误模式很隐蔽——它看着逻辑正确但在边界条件或时序细节上容易埋雷。核心理由是权限控制IP的正确性证明比功能IP更困难一个极端情况下才出现的竞态在功能仿真中几乎无法暴露需要形式化验证或非常仔细的代码审查。3.2 一个具体例子AI生成的AXI写通道握手代码以AI生成的AXI写地址通道握手为例初始代码大致是always_ff (posedge clk or negedge rst_n) begin if (!rst_n) begin awready 1b0; end else begin if (awvalid awready) begin awready 1b0; end else if (next_aw_ready_condition) begin awready 1b1; end end end这段代码在简单场景下没问题但它没有考虑两个关键点一是在outstanding事务多个未完成写事务并行时的AW通道流控二是当权限检查逻辑需要多个周期完成时AW握手信号与权限检查状态机的交互时序。我在此基础上重新手写了握手状态机把权限检查路径嵌入AW通道的valid-to-ready路径中确保awready只有在权限判定完成后才拉高。这个修改是安全的基石因为一旦awready拉高主设备就认为写事务已被接受后续无法再撤回。如果此时权限判定还没完成等于放行了未授权访问。3.3 AI辅助的收益评估客观说AI辅助在这个项目里节省的时间大约在20%-30%主要体现在代码骨架、寄存器模型、验证环境搭建上。但第一次使用AI辅助设计时很可能因为代码审查不仔细反而多花时间。我的体会是AI辅助IC设计的前提是设计者自己对整个架构有清晰的认知AI是加速工具而非替代者。4. 请求拦截节点的核心逻辑AXI通道上的过滤与转发4.1 拦截节点插在总线的哪个位置AXI总线的事务流向是从主设备如CPU核、DMA控制器发出经互联矩阵interconnect到达从设备如外设寄存器组。我把访问控制IP设计成两种接入方式方式一串接模式pass-through。访问控制IP在主机和外设之间作为一个代理节点所有请求先到达IPIP检查后再转发给真正的外设。这个方式简单直白但会引入一拍到两拍的额外延时。方式二旁路模式sideband。访问控制IP与互联矩阵并行通过互联矩阵的地址解码功能把指定地址区间的访问重定向到IPIP判定后把授权结果返回给互联矩阵。这个方式对正常路径延时影响小但需要互联矩阵配合。我做的是方式一因为它可以完全独立于互联矩阵实现便于模块复用也方便我做故障注入测试——我可以把IP从总线上单独摘下来给它人为制造各种异常请求来测试拦截逻辑。4.2 五个通道分别怎么处理AXI五个通道里AW写地址和AR读地址是请求入口W写数据、R读数据、B写响应是数据传输和响应通道。拦截逻辑的处理策略是AW和AR通道这两个通道是权限检查的入口必须做完整检查。W通道写数据通道本身不承载地址信息不单独做权限判定但要配合AW通道完成写数据的接收转发。R通道读数据返回通道当AR请求被拒绝时R通道要返回错误响应RRESPSLVERR而不是返回真实数据。B通道写响应通道当AW请求被拒绝时B通道要返回错误响应BRESPSLVERR同时不能把写数据W往目标外设转发。我的处理是AW和AR通道均采用先暂存请求、后判定、再转发的三段式流程。暂存的原因是权限判定需要时间而AXI协议的ready/valid握手一旦完成主设备就认为事务已发出所以必须把事务信息锁存后再进入判定逻辑。4.3 地址命中的并行匹配与优先级仲裁权限表有8个表项每个表项都有自己的地址比较逻辑所有表项并行比较然后仲裁出最高优先级匹配项。这个逻辑本身不复杂但工程细节比想象中多主要有两个点多个表项同时匹配时怎么办。比如一个表项配置为整块外设地址空间另一个表项配置为其中一段寄存器区间两个都命中时应该选择更精确掩码中1更多的那个表项。我的仲裁逻辑是掩码中1的数量越多优先级越高。这个规则用casez配合唯一匹配标识实现逻辑验证上比较好通过。没有表项匹配时怎么办。按照默认拒绝策略直接进入拒绝流程。这个并行匹配逻辑是我亲手写的AI生成的版本在优先级仲裁上出过问题——它会使用蛮力比较和级联MUX但综合后路径延迟很大。手动实现的版本使用排序网络结构从8个表项中选出掩码权重最大的那个综合后的关键路径在1GHz左右的保守工艺节点上仍能收敛。4.4 涉及burst事务的拦截细节AXI事务通常不是单拍访问而是一个burst连续传输比如INCR类型的增量突发传输一次可以传16拍。权限检查的粒度如果只覆盖首地址就会出现一个漏洞请求首地址在允许区间内但burst传输过程中地址递增越过了允许区间边界。我在设计中对burst事务做两类处理小burst长度不超过权限表项配置的burst_limit直接按首地址判定只要首地址落在合法区间且burst长度未超限就整体放行。大burst超过burst_limit则直接在地址通道拒绝同时返回错误响应。这个限制与AXI协议本身的高性能特性存在一定矛盾因为burst机制就是为了减少地址握手开销而设计的。但访问控制场景本来就是安全优先于性能——如果系统确实需要大数据块的连续传输正确做法是在权限表中先配置好大burst许可而不是试图在拦截节点做分段重发。5. 权限原子更新机制为什么原子性是刚需5.1 竞态问题的具体场景如果权限更新不是原子操作会有什么问题考虑这样一个场景外设A当前允许CPU访问权限表中对应表项valid1。系统安全策略更新需要撤销CPU对外设A的访问权限更新流程是先把valid清零再调整其他字段。在valid清零到其他字段调整完毕之间的某个时钟周期CPU恰好发出一个对外设A的读请求。但此时请求检查逻辑读取权限表其中部分字段是更新过程中的中间值可能正好命中了一个未配置完毕的表项导致误判允许。这个安全漏洞的本质是权限检查线程和权限更新线程同时访问同一份数据结构而数据结构的更新不是瞬时的。解决思路就是原子更新——要么更新前后的权限状态都对要么保持旧状态绝不能在中间状态上做判定。5.2 乒乓缓冲方案用空间换原子性我实现的原子更新用的是乒乓缓冲double buffering方案权限表在物理上存在两份称为shadow表和active表。CPU通过配置接口我采用的是AXI-Lite从接口写入shadow表的所有表项写完所有需要修改的表项后发出一个commit命令。commit命令在内部产生一个周期性的切换脉冲把active表切换到刚才写好的shadow表。由于active表指针的切换是一个单bit寄存器操作对于请求检查逻辑来说权限表整体的变化发生在同一个时钟沿上不存在中间状态。在shadow表写入过程中请求检查逻辑始终访问active表看到的是完整且一致的旧权限配置。乒乓缓冲的代价是面积翻倍但对于不到600位的小表来说这个代价完全可接受。它换来的是一个极其干净的语义检查逻辑永远不会看到一个写了一半的表。5.3 切换瞬间的边界处理真实工程里乒乓切换时有一个容易被忽略的边界切换命令发出的同一个时钟周期如果刚好有访问请求进来它看到的是新表还是旧表由于切换信号是一个寄存器输出对它进行采样的请求检查逻辑可能在切换沿前后出现一拍的差异。我的处理方式是在切换信号与请求判定逻辑之间插入一个同步门控当切换脉冲有效时请求检查逻辑暂停一个新的事务判定等待切换完成后恢复。这个暂停只持续一拍代价极小但能保证切换沿前后的请求都能明确归属到某一张完整的表。这一点一定要加断言盯住我就是在这里漏掉过才踩了坑。5.4 原子更新的验证策略原子更新这个特性功能仿真里最容易犯的错误是只在正常情况下验证而我把验证重点放在异常时序上。具体做法是在仿真中持续随机发起外设访问请求同时在后台随机执行权限表commit操作对比请求判定结果是否与某一份完整权限表快照一致。使用SystemVerilog断言可以形式化地表达这个不变量。故意构造切换边界请求——在commit信号有效的那一拍前先注入一个访问请求验证该请求使用的是旧权限表而不是新表或中间状态。尝试进行back-to-back commit也就是连续两次commit操作验证第二次commit不会造成权限表空白期。这三个验证点是我从一次真实漏洞中学到的教训如果只是用常规流程验证很难发现其中的隐患。6. 实操落地中的一些细节和踩坑记录6.1 总线协议适配最容易出错的两个地方使用AXI总线时有两个细节我特别想提醒并发事务的ID处理。AXI支持多个outstanding事务同时进行每个事务带有ID字段响应返回时需要与ID匹配。我的IP初始版本没有保存请求ID直接在拒绝返回时用了固定的ID导致在主设备有多个outstanding请求时响应ID错误。修正方法是在AW/AR暂存段中完整保存ID字段拒绝响应时原样返回。W数据的last信号处理。写数据通道是burst模式的AXI规定最后一个beat由WLAST标识。如果AW请求被拒绝但W通道的数据已经接收了部分此时需要将余下的W数据也一并接收并丢弃否则W通道会卡住后续的写事务全部阻塞。我在设计中使用了一个吸收状态当AW被拒绝后W通道进入absorb模式不断接收W数据和WLAST直到burst结束。这些细节如果只用AXI协议文档对照来查不易察觉但一旦在真实互联矩阵中接入问题马上暴露。我的排查方式是在VIP验证IP中跑乱序事务和长burst测试通过断言定位到具体的协议违例位置。6.2 表项更新与请求检查的缓存一致性问题乒乓缓冲解决了原子性但还有一个缓存一致性问题需要处理CPU通过AXI-Lite接口写shadow表时写操作本身是串行写寄存器的。如果CPU在写表过程中被中断表项可能是半更新状态。此时commit命令还没有发出active表依然是旧值这是安全的。但如果CPU在写表中途重新修改了某个表项之后又发出commit这会导致部分旧值、部分新值的混合表被激活。我对此的约定是CPU必须按表项序号连续写入每个表项的全字段并且提供修改完成确认寄存器IP检查确认寄存器后发现字段完整才允许commit。如果不做这个检查乒乓缓冲的原子切换语义就会被上层乱序写击穿。6.3 默认拒绝策略在调试期的坑deny-all策略在安全上是正确的但在调试期特别烦人。最初跑系统级验证时外设驱动偶尔访问被拒排查了很久才发现是驱动代码在初始化时先写了一个保留寄存器而权限表没包含这个地址段。我后来在IP里增加了一个debug模式寄存器调试模式下所有请求放行但记录拒绝日志正式模式下才启用deny-all。这个debug模式同时受一个硬件熔丝位控制芯片量产时可以永久关闭防止生产环境被错误配置成调试模式。这个设计思路是从JTAG安全策略借鉴来的简单但实用。6.4 时序与面积的实际数据设计使用28nm工艺库综合目标频率1GHz。整个IP面积约2.1万门相对于典型SoC的数百万门规模完全可忽略。时序上影响关键路径的是权限表8路并行匹配后的仲裁逻辑完整路径从输入到输出授权信号约1.4ns可以满足时序要求。如果目标频率更高或表项更多可以把匹配结果按地址位分段预解码把仲裁MUX延迟分散到两级流水代价是多一拍判定延迟。做设计时要权衡每多一拍检查延迟和频率提升之间的关系没有绝对正确答案取决于系统对总延迟的容忍度。7. 后续扩展方向与个人体会7.1 可以做的几个扩展方向这个IP后续有几个很自然的扩展方向增加统计计数和审计日志。每个拒绝事务可以记录来源ID、地址、时间戳这些信息写入环形缓冲由安全软件定期读取。支持动态权限表索引。比如根据系统运行模式启动、正常运行、安全降级切换到不同的权限表配置。扩展到AHB或APB总线版本或者做AXI-Stream接口版本用于控制DMA流式数据的访问权限。把访问控制判决从纯组合逻辑升级为可编程规则引擎支持更灵活的匹配条件。7.2 关于AI辅助设计外设访问控制IP的最终建议回到开头的问题AI在这类安全关键设计中到底能帮多少我的结论是AI可以当协设计师但它不能替代设计者对安全语义的理解。权限控制IP的正确性不是一个函数功能测试能覆盖的它需要你对每个总线事务的每个可能行为都了然于胸。给正准备尝试AI辅助硬件设计的工程师三条建议先用传统方式做过至少一个完整的安全类IP建立对时序、握手、竞态的直觉再让AI参与迭代这样效率提升才明显。AI生成的控制逻辑代码要拿到综合工具里做严格的CDC检查和Lint检查不要因为功能仿真通过了就放松警惕。所有安全策略相关的代码——权限表匹配、更新仲裁、默认拒绝路径——都应当被视为关键代码逐行人工评审条件允许的话配合形式化验证工具做属性检查。最后再分享一个经验权限表匹配逻辑和原子更新逻辑是我整个IP里修改次数最多的两个模块原因都是边界条件考虑不全。如果你也打算做类似的访问控制模块我建议在最开始就把默认拒绝和原子切换这两条原则固化到架构层面而不是放在代码层面边写边补。架构上先做对代码实现就会顺畅很多。
返回列表