2. SystemVerilog 接口基础:接口定义语法、接口端口声明、模块连接方式
接口(Interface)是 SystemVerilog 相对于传统 Verilog 最重要的结构化特性之一。它将一组信号(线网、变量、任务、函数、断言)封装成一个独立的实体,使得模块之间的连接从“逐信号连线”升级为“单点绑定”。本节从语法、端口声明和模块连接三个层面建立接口的基础认知。
2.1 接口定义语法
接口使用 interface 关键字定义,内部可以包含信号声明、时钟块、断言、任务和函数。基本语法结构如下:
interface interface_name [参数列表] [modport 列表];
// 信号声明
logic [7:0] data;
logic valid;
logic ready;
// 时钟块(可选)
clocking cb @(posedge clk);
input data, valid;
output ready;
endclocking
// 任务或函数(可选)
task send_data(input [7:0] d);
data = d;
valid = 1;
@(posedge ready);
valid = 0;
endtask
// modport 定义(可选)
modport master (input ready, output data, valid);
modport slave (input data, valid, output ready);
endinterface
| 语法元素 | 说明 | 示例 |
|---|---|---|
interface … endinterface | 定义接口的起始与结束 | interface bus_if; |
| 信号声明 | 内部信号,类型与 Verilog 一致 | logic [31:0] addr; |
modport | 定义信号方向视图 | modport master (output addr); |
clocking | 定义同步采样/驱动时序 | clocking cb @(posedge clk); |
| 任务/函数 | 封装协议操作 | task read(input addr); |
要点:接口本身不包含
input/output 方向——方向由 modport 或模块连接时的端口绑定决定。接口内部的信号默认是双向的(类似 wire),方向约束通过 modport 施加。
2.2 接口端口声明
接口可以像模块一样拥有参数和端口,但接口的“端口”通常指接口内部暴露给外部的信号集合。实际使用中,接口端口声明有两种常见形式:
2.2.1 无参数接口
interface simple_if;
logic clk;
logic rst_n;
logic [7:0] data;
logic valid;
endinterface
2.2.2 参数化接口
interface param_if #(parameter WIDTH = 8, parameter DEPTH = 16);
logic clk;
logic rst_n;
logic [WIDTH-1:0] data;
logic [$clog2(DEPTH)-1:0] addr;
logic wr_en;
endinterface
| 声明方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 无参数接口 | 固定位宽、固定协议 | 简单直接,易读 | 复用性差 |
| 参数化接口 | 可配置位宽、深度 | 高复用,适应不同设计 | 参数传递需谨慎 |
| 含 modport 接口 | 多角色连接(master/slave) | 方向清晰,避免误驱动 | 定义稍复杂 |
常见错误:在接口内部使用
input/output 声明信号。接口内部信号应使用 logic 或 wire,方向通过 modport 或模块端口绑定来指定。直接在接口内写 input logic clk 会导致编译错误。
2.3 接口与模块的连接方式
接口与模块的连接有三种主流方式,按使用频率从高到低排列:
- 直接传递接口实例 —— 模块端口声明为接口类型,顶层传递接口句柄。
- modport 定向连接 —— 在接口实例后附加 modport 名称,指定信号方向。
- 接口数组连接 —— 将多个接口实例组成数组,用于总线矩阵或多通道场景。
2.3.1 直接传递接口实例
// 接口定义
interface bus_if;
logic [31:0] addr;
logic [31:0] data;
logic we;
endinterface
// 模块端口使用接口类型
module master (bus_if mif);
// 使用 mif.addr, mif.data, mif.we
endmodule
module slave (bus_if sif);
// 使用 sif.addr, sif.data, sif.we
endmodule
// 顶层连接
module top;
bus_if bus_inst();
master u_master (.mif(bus_inst));
slave u_slave (.sif(bus_inst));
endmodule
2.3.2 modport 定向连接
interface bus_if;
logic [31:0] addr;
logic [31:0] data;
logic we;
modport master (output addr, data, input we);
modport slave (input addr, data, output we);
endinterface
module master (bus_if.master mif); // 使用 modport
// mif.addr 是 output,mif.we 是 input
endmodule
module slave (bus_if.slave sif);
// sif.addr 是 input,sif.we 是 output
endmodule
2.3.3 接口数组连接
interface channel_if;
logic [7:0] payload;
logic sop, eop;
endinterface
module router (channel_if.inp ports[4]); // 4 个输入通道
// 使用 ports[0].payload, ports[1].sop 等
endmodule
module top;
channel_if ch[4](); // 声明 4 个接口实例
router u_router (.ports(ch));
endmodule
连接方式对比总结:
- 直接传递:最简洁,适合信号方向由模块内部逻辑保证的场景。
- modport 连接:方向显式化,适合 master/slave 多角色复用同一接口。
- 接口数组:适合交叉开关、路由器、多通道 DMA 等结构化设计。
工程建议:在验证环境中,推荐为每个协议定义独立的接口文件,并在接口内同时提供
modport 和 clocking 块。这样 driver、monitor、scoreboard 可以直接通过 modport 连接,避免信号方向错误。接口参数化后,同一个接口文件可以适配不同位宽的设计,大幅减少重复代码。