ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SystemVerilog Interface 详解:从端口连接到验证哲学与工程实践

SystemVerilog Interface 详解:从端口连接到验证哲学与工程实践 1. 从物理连线到验证哲学的进化Interface 到底解决了什么问题做芯片验证的人每天打交道最多的抽象层次就是信号。早年间写 RTL 或者搭 testbench 的时候我们都在跟 port 列表打交道。一个模块有几十个端口一点也不奇怪时钟、复位、数据总线、控制信号、握手信号一层一层传下去。顶层模块动辄上百个端口连线每次增删一个信号就得在所有引用该模块的地方同步维护漏改一个地方就是编译报错或者更强的隐蔽错误。SystemVerilog Interface 这个语法最初的动机说白了就是把一组相关的信号捆绑成一个独立的复合端口。它不是新鲜概念类似于 PCB 设计里的排线、软件里的结构体核心思路都是把经常一起出现的东西包在一起。但 Interface 能流行起来绝不只是因为它能减少端口数量。它真正的价值在于把物理连线这个动作升级成了协议建模这个动作。你在 Interface 里不只是定义了一堆 wire 和 reg你可以在里面定义 modport不同视角的信号方向、clocking block同步时序约束、函数和任务协议行为、parameter参数化接口宽度和时序、甚至断言和覆盖率属性。这意味着同一个接口不仅能作为 RTL 模块之间的连接还能直接作为验证环境与 DUT 之间的通信管道。我经常跟刚入行的同学说如果把一个协议理解成一种沟通方式那 Interface 就是这个沟通方式的实体化。你把握手规则、信号方向、时序要求在接口里约定清楚后续无论是写 RTL 还是搭 UVM 环境都基于这个约定来做事而不是各写各的、靠工程师之间口头对口径。这篇文章我想从物理连接、协议抽象、验证方法论三个层面把 SystemVerilog Interface 完整拆开来讲。后面所有的例子都是我实际在项目里用过或者踩过坑之后整理出来的尽量贴近真实工程场景。2. 物理连接层的正确打开方式端口列表的噩梦和 Interface 的解药2.1 传统端口列表的维护地狱先还原一个场景。你有一个 AXI 从设备接口信号包括 AW channel 的地址、ID、长度、大小、突发类型、锁定、缓存、保护、QoS、有效、就绪数据通道的写数据、写选通、写有效、写就绪、写响应读通道和读响应通道再加上低功耗相关信号一共五六十个端口很常见。顶层例化的时候每个端口一行RTL 文件几百行全是端口连接。更痛苦的是如果设计中需要给 AXI 总线增加一个用户自定义信号比如调试用的 markers你得在所有用到这个接口的地方修改端口列表。仿真环境里 DUT 例化要改UVM agent 的虚接口声明要改断言绑定的信号名要改。人在这种重复劳动中容易出错而这一类错误往往不是语法错误是看起来对但实际上连错的逻辑错误排查起来非常费时间。2.2 Interface 的物理连接本质Interface 定义了一组信号的集合在模块端口列表中只需要声明一个 interface 端口。我们看一个最基本的例子interface axi_if #( parameter int ADDR_WIDTH 32, parameter int DATA_WIDTH 64 )( input logic aclk, input logic areset_n ); logic [ADDR_WIDTH-1:0] awaddr; logic [7:0] awid; logic [7:0] awlen; logic [2:0] awsize; logic [1:0] awburst; logic awvalid; logic awready; // ... 其他 AXI 信号 modport master( output awaddr, awid, awlen, awsize, awburst, awvalid, input awready // ... 其他方向定义 ); modport slave( input awaddr, awid, awlen, awsize, awburst, awvalid, output awready // ... 其他方向定义 ); endinterface模块端口就简洁多了module axi_slave #( parameter int ADDR_WIDTH 32, parameter int DATA_WIDTH 64 )( axi_if.master axi_bus ); // 内部逻辑直接通过 axi_bus.awvalid、axi_bus.awready 访问信号 endmodule注意axi_if.master这种带 modport 的端口声明方式。这相当于是说我实例化一个 axi_if 类型的端口在这个模块里我以 master 的身份来使用它。方向由 modport 定义而不是由模块端口方向决定。这时候加一个新信号你只需在 interface 里加一行信号定义并在相关 modport 里加一行方向。所有例化这个 interface 的地方除非你也把新信号引出来使用否则都不需要改动。这一下子把端口维护的边际成本降到了接近零。2.3 物理连接实战中的三个关键注意点第一个注意点interface 里的信号可以是 wire 也可以是 logic。如果某个信号在多个 always 块或连续赋值中被驱动需要声明为 wire或者用 nets。但 interface 作为模块端口使用时信号类型其实还有一层特殊规则interface 端口本身是类似 module port 的存在不能把它简单看作一个普通信号端口。它比普通信号端口严格的多比如你不能在 interface 上直接用 assign 去连续赋值除非是 interface 内部或特殊方式需要遵循 SystemVerilog 对于 interface port 的限制。第二个注意点interface 端口需要在模块端口列表中声明为 interface 类型但顶层例化的时候不能像普通端口一样按位置连接需要使用.接口名(接口实例名)的方式。顶层例化的代码大概长这样axi_if #( .ADDR_WIDTH(32), .DATA_WIDTH(64) ) u_axi_bus ( .aclk(clk), .areset_n(rst_n) ); axi_slave u_slave ( .axi_bus(u_axi_bus) );这里的.axi_bus(u_axi_bus)是一个接口实例的连接。连接接口实例时不能只连接接口里的一部分信号要么全连要么不连这是接口整体的特性。第三个注意点如果接口信号中包含 inout 类型比如 I2C 的 SDA 引脚仿真和综合里会有一些额外限制。在 fabric 里使用带 inout 的 interface 时需要特别小心因为 inout 信号需要三态驱动而 interface 内部对 inout 的支持受仿真器限制较多某些综合工具也未必支持得很完善。我在项目里处理这类信号时一般倾向于在 interface 里只放逻辑信号把真正的 inout pad 留在顶层处理通过控制信号切换方向而不是直接在 interface 里声明 inout。3. 协议抽象modport、clocking block 和 parameter 的组合拳3.1 modport 是连接角色的约定不是物理方向modport 在不同模块视角下定义信号方向它本质上是视角而不是方向。同一个 interface 端口master 视角里awvalid是 outputslave 视角里就是 input。物理上信号的来源和去向没有变变的只是观察者的立场。这个抽象的价值在验证环境里体现得最为充分同一个 interface 连接到 DUT 和连接到 driver 时方向视角完全不同但它们访问的是物理上同一组信号。用错了 modport 会导致编译错误但要注意一个问题不同 modport 里的同名信号方向必须一致。比如在 master modport 里把awvalid定义成 output在 slave modport 里就不能定义成 output应该 input否则仿真器会报方向冲突。这个逻辑其实很好理解信号在一个时刻只能有一个驱动源。3.2 clocking block把时序收敛在接口里面clocking block 是 Interface 里最强也最容易被忽视的特性。它解决了验证环境里一个古老的问题激励和采样的事件时序控制。传统 Verilog testbench 里驱动信号和采样信号经常基于(posedge clk)来写但同样的写法由于仿真器调度队列的细节可能导致竞争冒险。比如在always (posedge clk)阻塞赋值里驱动一个信号另一个 always 也在同一时刻采样谁先谁后取决于代码顺序和仿真器的调度优化这种不确定性在复杂验证环境里是致命的。clocking block 通过声明采样沿和驱动沿的偏移把时序关系明确固化下来interface axi_if #( parameter int ADDR_WIDTH 32, parameter int DATA_WIDTH 64 )( input logic aclk, input logic areset_n ); // 信号定义 ... clocking cb (posedge aclk); default input #1step output #0; output awvalid, awaddr, awid, awlen, awsize, awburst; input awready; // ... endclocking modport master_mp(clocking cb); endinterface上面default input #1step output #0的含义是采样时比时钟沿提前一个步长#1step这样可以采到时钟沿前稳定的值驱动时在时钟沿后延迟 0 个仿真时间单位也就是利用非阻塞特性在时钟沿之后更新信号。这两个默认偏移是 UVM 社区里最常用的配置我个人也一直建议新手照抄这个默认值除非你有明确的理由去修改。在验证环境里driver 的代码可以这样写task run_phase(); forever begin (vif.cb); vif.cb.awvalid 1b1; vif.cb.awaddr addr; end endtask(vif.cb)是在等待 clocking block 的时钟事件。驱动信号时直接通过vif.cb.awvalid ...这个赋值会被 clocking block 自动对齐到正确的驱动沿你不需要再手动写(posedge clk)配合延迟。采样时vif.cb.awready则会按照#1step的规则采到稳定的值。一个容易踩坑的地方clocking block 里的信号不能被 RTL 模块直接引用。clocking block 本质上是面向验证环境的同步机制RTL 侧不需要也无法使用它。所以如果你把一个带 clocking block 的 interface 端口连接到 RTL 模块端口该模块访问的是 interface 中的原始信号比如vif.awvalid而不是 clocking block 里的vif.cb.awvalid。3.3 parameter 参数化 Interface宽度和时序统统可变前文例子里的#(parameter int ADDR_WIDTH 32)已经展示了 Interface 的参数化能力。你可以把地址宽度、数据宽度、突发长度、甚至时钟频率和延迟参数都参数化。这样同一个 interface 定义可以被不同配置的子系统复用避免复制粘贴导致的分叉。参数化之后所有依赖参数的地方如logic [ADDR_WIDTH-1:0] awaddr都自动适配代码的可复用性和可配置性大幅提升。有一个细节interface 的参数可以在模块端口声明时重新赋值。例化 AXI slave 时如果它的接口端口是axi_if.master而实际连接的是一个axi_if #(.ADDR_WIDTH(64), .DATA_WIDTH(128))的实例那么端口声明里的参数必须与实际实例匹配。这里容易出错编译时仿真器会检查参数匹配但检查方式可能不够直观错误信息会让人一头雾水。遇到这种问题时先把参数列表对齐基本都能解决。3.4 接口里的函数和任务协议行为的聚合Interface 里允许定义函数和任务这些函数和任务对于连接同一接口的多个模块是可见的。这在协议建模里特别好用比如把 AXI 读写的任务封装在 interface 里interface axi_if #(...); // 信号定义 ... task automatic read(input logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data); // 发起读事务 awvalid 1b1; awaddr addr; (posedge awready); // ... endtask endinterface模块内部直接调用vif.read(addr, data)。这种方式让协议行为和物理信号封装在一起RTL 侧或验证侧不需要各自实现一套协议时序逻辑。但要注意在可综合 RTL 中定义和使用这类任务综合工具的支持不一定完整。一般来说接口内函数任务的主要用途在验证环境和系统级建模而不是可综合 RTL。4. 验证世界里的 Interfacevirtual interface 才是核心4.1 为什么验证环境不能直接用 interface 实例验证工程师接触 Interface 最多的地方其实是virtual interface。你可能已经注意到前文所有 RTL 模块端口连接 interface 的方式都是实际接口实例。但在 UVM 环境里我们很少直接在 class 里声明一个 interface 实例而是用virtual interface。原因有两点。第一interface 是一个硬件结构或者说静态实例不能在uvm_component这样的软件对象里直接例化。第二验证环境需要的是对接口的引用而不是接口的所有权。virtual interface 本质上是一个透明的引用句柄它不拥有接口只提供访问通道。4.2 virtual interface 的标准用法在 TESTBENCH 顶层里实例化 interface然后通过 config_db 把 virtual interface set 进去agent 里再从 config_db get 出来。下面是一个完整的流程示例。顶层module tb_top; logic clk; logic rst_n; axi_if #(.ADDR_WIDTH(32), .DATA_WIDTH(64)) u_axi_bus(.aclk(clk), .areset_n(rst_n)); axi_slave u_slave (.axi_bus(u_axi_bus)); // UVM 环境通过 virtual interface 访问 u_axi_bus initial begin uvm_config_db#(virtual axi_if)::set(null, uvm_test_top.env.agent.*, vif, u_axi_bus); end endmoduleagent 里class axi_agent extends uvm_agent; virtual axi_if vif; function void build_phase(uvm_phase phase); super.build_phase(phase); if (!uvm_config_db#(virtual axi_if)::get(this, , vif, vif)) uvm_fatal(AGENT, Cannot get virtual interface) endfunction endclass这个模式是 UVM 验证环境的骨干几乎所有总线协议 agentAXI、APB、AHB、I2C、SPI都是这么干的。4.3 踩过高频坑virtual interface 的多态和 null 处理一个常见坑是 config_db 的路径字符串写错导致 get 不到 virtual interface报vif为 null。这种错误排查起来比较费劲因为不是编译错误而是运行到使用vif的那一刻才崩溃或行为异常。我建议在 agent 里 get 完之后立刻检查 null并打印一条 info 信息比如uvm_info(AGENT, $sformatf(Got vif: %s, vif ! null ? OK : NULL))这样仿真日志里一眼能看出有没有配置成功。另一个坑是 virtual interface 指向了带 clocking block 的 modport而你的 driver 是通过vif.cb来访问信号的。如果你在 config_db 里 set 的 interface 实例和 agent 里声明的virtual axi_if类型不匹配比如一个是带 modport 的一个不带编译期可能不报错但运行期会有访问错误。所以保持一致的类型声明非常重要set 和 get 两边的类型必须完全一致最好在 set 时也显式指定类型。4.4 Interface 在断言绑定中的另类价值除了连接和驱动Interface 还可以用来挂断言。SVA 断言可以直接写在 interface 里面比如property p_awvalid_ready; (posedge aclk) awvalid |- ##[1:$] awready; endproperty assert property(p_awvalid_ready) else uvm_error(AXI_ASSERT, AWVALID assertion failed)这样做的好处是断言与协议定义绑定在一起只要 interface 被例化到哪里断言就会跟到哪里不用额外写 bind 文件。在调试时断言直接引用 interface 内部信号非常方便。唯一的注意点有些团队习惯把 assertion 单独放在 bind 文件里以方便统一开关比如用宏控制编译。在 interface 内嵌断言时如果要禁用某些断言需要用assertion disable或编译宏来控制方式相对受限。5. 仿真与综合的分野Interface 的两个世界5.1 RTL 模块之间的 Interface 连接到底能不能综合这里必须说清楚一个让很多人困惑的问题interface 是 SystemVerilog 语法现代综合工具如 Design Compiler、Genus对 interface 的综合支持已经比较成熟但并非所有写法都能综合。基本的信号捆绑、modport 方向、parameter 参数化主流工具都是支持的。但 clocking block 和 interface 里定义的 task/function综合工具通常不支持或者只支持有限子集。因此实际项目中综合用的 RTL 和验证用的 testbench 往往使用不同风格的 interface 写法。5.2 一个接口定义两种使用方式为了兼顾综合和验证我见过的最常见做法是interface 里只放信号和 modport不放 clocking block、不放 task/function验证侧通过virtual interface clocking block 的另一种定义或者直接在 driver 里做时序控制来实现同步驱动。也就是说把协议定义和验证便利性分开物理接口只承担连接职责。当然也有更激进的团队把 clocking block 放进 interface 里然后通过宏或者 generate 来控制在综合时不生成。这种方法理论上可行但代码可读性和维护成本会上升尤其是团队协作时不同工程师对宏开关的理解不一致容易出问题。我个人的偏好是简化接口定义把 clocking block 留到验证环境中用另一种方式处理。5.3 综合工具对 Interface 支持的历史演变早期的综合工具对 interface 支持很差很多团队禁止在 RTL 中使用 interface只准在 testbench 里用。后来随着 SystemVerilog 设计方法论在业界普及几大综合工具陆续支持了 interface 的常见子集。现在的 2025 年主流综合工具对基本 interface modport parameter 的支持已经非常成熟但如果你要用 interface 里的 generate 逻辑、或把 interface 作为一个整体做跨时钟域处理还是需要仔细查证工具文档。对于还在使用老综合工具或特定 FPGA 流程的团队我建议通过小样例提前验证写一个使用 interface 的简单模块跑一遍综合和布局布线确认无误后在大规模设计中放手用。这个验证成本很低能避免后期大规模返工。6. 工程实战参数化 AXI Interface 完整示例6.1 示例背景和设计目标这里我给一个完整的实战示例目标是构建一个可复用的参数化 AXI4-Lite 从设备接口既能用于 RTL 模块连接又能用于 UVM 验证环境。AXI4-Lite 相比完整 AXI4 信号少一些适合用来演示核心概念。整个接口的定义包括参数化地址数据宽度、写地址通道、写数据通道、写响应通道、读地址通道、读数据通道以及 master/slave modport。为了保持可综合性暂不引入 clocking block 和 task/function验证侧的同步控制放在 driver 里实现。6.2 接口定义完整代码interface axilite_if #( parameter int ADDR_WIDTH 32, parameter int DATA_WIDTH 32 )( input logic aclk, input logic areset_n ); // 写地址通道 logic [ADDR_WIDTH-1:0] awaddr; logic [2:0] awprot; logic awvalid; logic awready; // 写数据通道 logic [DATA_WIDTH-1:0] wdata; logic [DATA_WIDTH/8-1:0] wstrb; logic wvalid; logic wready; // 写响应通道 logic [1:0] bresp; logic bvalid; logic bready; // 读地址通道 logic [ADDR_WIDTH-1:0] araddr; logic [2:0] arprot; logic arvalid; logic arready; // 读数据通道 logic [DATA_WIDTH-1:0] rdata; logic [1:0] rresp; logic rvalid; logic rready; modport master( output awaddr, awprot, awvalid, wdata, wstrb, wvalid, output bready, araddr, arprot, arvalid, rready, input awready, wready, bresp, bvalid, rdata, rresp, rvalid ); modport slave( input awaddr, awprot, awvalid, wdata, wstrb, wvalid, input bready, araddr, arprot, arvalid, rready, output awready, wready, bresp, bvalid, rdata, rresp, rvalid ); endinterface这个接口是我在多个项目中验证过的模板。使用它时RTL 模块端口如下module axilite_sram #( parameter int ADDR_WIDTH 16, parameter int DATA_WIDTH 32 )( axilite_if.slave s_axil ); // SRAM 逻辑 // 直接通过 s_axil.awvalid 等信号访问 endmodule顶层例化时axilite_if #(.ADDR_WIDTH(16), .DATA_WIDTH(32)) u_axil_bus(.aclk(clk), .areset_n(rst_n)); axilite_sram #(.ADDR_WIDTH(16), .DATA_WIDTH(32)) u_sram ( .s_axil(u_axil_bus) );6.3 验证侧的 virtual interface 使用示例验证环境里 agent 的 driver 通过 virtual interface 驱动这个总线。我这里给出一个简单的 driver 片段class axilite_master_driver extends uvm_driver #(axilite_transaction); virtual axilite_if vif; task run_phase(uvm_phase phase); (posedge vif.aclk); // 初始化信号 vif.awvalid 1b0; vif.wvalid 1b0; vif.arvalid 1b0; vif.rready 1b1; vif.bready 1b1; forever begin seq_item_port.get_next_item(req); // 根据事务类型驱动对应通道 if (req.kind axilite_transaction::WRITE) write_transaction(req); else read_transaction(req); seq_item_port.item_done(); end endtask task write_transaction(axilite_transaction req); // 写地址 (posedge vif.aclk); vif.awvalid 1b1; vif.awaddr req.addr; vif.wvalid 1b1; vif.wdata req.data; vif.wstrb {(DATA_WIDTH/8){1b1}}; do (posedge vif.aclk); while (!vif.awready || !vif.wready); vif.awvalid 1b0; vif.wvalid 1b0; // 等写响应 do (posedge vif.aclk); while (!vif.bvalid); vif.bready 1b1; (posedge vif.aclk); vif.bready 1b0; endtask endclass这段代码没有使用 clocking block驱动时序靠 driver 内部的(posedge vif.aclk)来控制。好处是直观坏处是需要小心采样和驱动的竞争问题。在实际项目中如果信号时序比较复杂我仍然建议结合 clocking block 使用但 cloking block 的引入会影响接口定义的可综合性所以这是一个平衡问题。6.4 参数匹配与 generate 条件例化在大型 SoC 里同一个接口类型经常被多个模块使用但各自参数不同。这里有一个很实际的技巧不要在一个 interface 定义里塞所有可能的参数而是根据需求设计合理的默认值。默认值的作用是在不显式覆盖参数的情况下模块和接口的参数能自动匹配减少错误。另外如果你需要在接口内部针对不同参数生成不同的逻辑比如校验位宽度变化时生成不同的奇偶校验逻辑可以使用 generate。但请注意interface 里的 generate 语法在综合工具里支持程度不一建议先验证。我常用的方式是如果在 RTL 里需要根据参数生成不同逻辑我会选择在模块内部做 generate而不是在 interface 里做。7. 深入理解 Interface 背后的验证哲学7.1 从信号集合到协议实体回顾整个 interface 的演进我认为它背后隐含的验证哲学是从面向信号的思维转向面向协议的思维。传统 Verilog 时代我们经常把协议放在每个模块内部自己实现。比如APB 的写时序在发起端写一遍在接收端再写一遍。两边的逻辑可能有一百种方式呈现相同的协议特征那验证时就得针对每一种实现方式去适配。如果协议变化比如增加等待周期所有模块的代码都要跟着改。Interface 改变了这种模式。协议在一处定义、多处复用。模块之间通过接口交互而不是通过分散的信号交互。这其实很像软件工程里的接口隔离原则和依赖倒置原则底层细节信号连接被封装在接口里高层次的模块只需要依赖接口的约定而不需要关心实现细节。7.2 Interface 促进了设计与验证的协同演进还有一个非常实际的哲学价值Interface 使得设计与验证团队能在同一个合同下工作。在项目初期设计工程师和验证工程师可以共同定义一个接口包括信号列表、参数、modport这个接口成为双方协作的契约。设计侧基于它实现 RTL验证侧基于它搭建 testbench两侧的工作可以高度并行而不会出现接口理解不一致的问题。这种协同在我们的实际项目里非常有用。以前没有 Interface 时设计端口一改验证环境跟着改两边反复沟通成本很高。有了 Interface这个改动被局部化谁影响谁、影响多大一目了然。从项目管理的角度说Interface 是一个清晰的技术边界让团队的职责划分和工作流更顺畅。7.3 Interface 与断言、覆盖率的天然绑定在验证哲学层面Interface 还有一个容易被低估的价值它天然适合承载协议断言和功能覆盖率。协议级的行为属性比如写地址和写数据必须在同一周期有效、读地址有效后必须在有限周期内返回数据不属于任何单一模块而是发生在模块之间的交互中。把这类断言定义在 Interface 里是最自然的位置选择。覆盖率点也一样比如awvalid 和 awready 同时拉高的周期数是总线级指标放在 interface 里比放在某个 agent 里更合理。当然在 UVM 环境中很多团队更喜欢把覆盖率点和断言放在独立的 interface coverage 类或 SVA bind 文件里。这并不冲突关键是要有一个明确的归属原则。我个人的习惯是与协议强相关的属性放 interface与验证场景相关的覆盖率放 testbench 环境。这样职责分明代码也更容易维护。8. 实操中的坑与排查逻辑8.1 编译顺序和包依赖使用 Interface 时模块端口里引用 interface 类型编译器必须在解析模块之前先编译 interface 定义。这个顺序问题在大型工程里很常见特别是当 interface 文件被多个子模块引用时。如果在编译时出现类似 Unknown type: axi_if 的错误先检查文件编译顺序或者是否在正确的库中编译。8.2 同名信号遮蔽问题interface 内部的信号名与模块内部的局部变量或端口名冲突时编译器会报重复声明或者出现遮蔽。排查这类问题建议在 interface 里统一信号命名风格比如加前缀前缀i_、o_不够规整可以用aw_、ar_、w_等或者在模块内部访问接口信号时始终显式使用vif.信号名的方式避免局部同名变量干扰。8.3 clocking block 的采样精度被忽略的 #1step很多人第一次看到default input #1step output #0时不知道#1step到底是什么。一个 time step 是仿真器的最小时间精度单位#1step指比当前时间步提前一个精度单位。在高精度仿真里这个提前能有效规避采样竞争。反之如果写成input #0在某些仿真器里可能采到时钟沿同时被驱动的信号而不是稳定的旧值。这一点在 SOC 级验证中尤为重要因为跨模块的时序关系复杂任何采样不确定性都会放大。8.4 速度与调试过度使用 Interface 的代价说了这么多优点也需要泼点冷水。Interface 不是越多越好。如果一个接口只被一个模块使用、信号只有两三个那把它包装成 interface 反而增加了代码跳转成本。调试时信号在 interface 内部间接访问仿真器波形里查看信号需要展开一层略麻烦。另外有些验证工程师初学 SystemVerilog 时会陷入万物皆 interface的误区连简单的握手都包一个 interface导致文件爆炸、层级混乱。合理的粒度是复用次数 2 且信号之间有明确的协议关联再考虑用 interface。9. 后续可以扩展的方向SystemVerilog Interface 的内容远不止这些。比如 interface 与 concurrent assertions 的深度结合、interface 在 formal verification 中的应用、interface 里的 modport 相互转换技巧、interface 作为参数传递到 class 的完整设计模式等等。如果未来有时间我想再写一篇关于 UVM 环境里 interface 与 config_db 复杂路径管理最佳实践的分享毕竟这块在大型项目中是高发问题区。另一个值得深入的方向是 interface 与 power intentUPF的交互不过这已经涉及到低功耗验证了内容会比这篇更深一档。回到最开头的问题Interface 为什么值得深入理解因为它解决了从物理连接到验证哲学的两个层次的问题前者省钱省力后者让团队协作更有序。这波投入非常值。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进