【SystemVerilog】高级接口

第十章

目录

ATM路由器的虚接口

只含有物理接口的测试平台

[例 10.1 带有时钟块的 Rx 接口](#例 10.1 带有时钟块的 Rx 接口)

[例 10.2 带有时钟块的 Tx 接口](#例 10.2 带有时钟块的 Tx 接口)

[例 10.3 使用物理接口的测试平台(硬编码方式)](#例 10.3 使用物理接口的测试平台(硬编码方式))

[例 10.4 含有接口数组的顶层模块](#例 10.4 含有接口数组的顶层模块)

使用虚接口的测试平台

[例 10.5 使用虚接口的测试平台](#例 10.5 使用虚接口的测试平台)

[例 10.6 使用接口数组的测试平台(更简洁)](#例 10.6 使用接口数组的测试平台(更简洁))

[例 10.7 使用虚接口的驱动器类](#例 10.7 使用虚接口的驱动器类)

将测试平台连接到端口列表中的接口

[例 10.8 使用端口列表中的接口的测试用具](#例 10.8 使用端口列表中的接口的测试用具)

[例 10.9 端口列表含有接口的测试程序](#例 10.9 端口列表含有接口的测试程序)

[例 10.10 端口列表中含有第二个接口的顶层模块](#例 10.10 端口列表中含有第二个接口的顶层模块)

[例 10.11 端口中含有两个接口的测试程序](#例 10.11 端口中含有两个接口的测试程序)

[使用 XMR(跨模块引用)连接接口和测试程序](#使用 XMR(跨模块引用)连接接口和测试程序)

[例 10.12 使用虚接口和 XMR 的测试程序](#例 10.12 使用虚接口和 XMR 的测试程序)

[例 10.13 端口列表中不含接口的测试用具](#例 10.13 端口列表中不含接口的测试用具)

[例 10.14 使用第二个接口的测试用具(测试程序无需改变)](#例 10.14 使用第二个接口的测试用具(测试程序无需改变))

[例 10.15 使用两个虚接口和两个 XMR 的测试程序](#例 10.15 使用两个虚接口和两个 XMR 的测试程序)

[物理接口 vs 虚接口 vs XMR 对比](#物理接口 vs 虚接口 vs XMR 对比)

两种连接风格对比

连接到多个不同的设计配置

网格(Mesh)设计案例

[例 10.16 8 位计数器的接口](#例 10.16 8 位计数器的接口)

[例 10.17 使用 X_if 接口的计数器模型](#例 10.17 使用 X_if 接口的计数器模型)

[例 10.18 使用虚接口数组的测试平台顶层模块](#例 10.18 使用虚接口数组的测试平台顶层模块)

[例 10.19 使用虚接口的计数器测试平台](#例 10.19 使用虚接口的计数器测试平台)

[例 10.20 使用虚接口的 Driver 类](#例 10.20 使用虚接口的 Driver 类)

[对虚接口使用 typedef](#对虚接口使用 typedef)

[例 10.21 对虚接口使用 typedef 的测试平台](#例 10.21 对虚接口使用 typedef 的测试平台)

[例 10.22 在驱动器类型使用 typedef 的虚接口](#例 10.22 在驱动器类型使用 typedef 的虚接口)

使用端口传递虚接口数组

[例 10.23 使用虚接口数组的顶层模块(端口方式)](#例 10.23 使用虚接口数组的顶层模块(端口方式))

[例 10.24 使用端口传递虚接口的测试平台](#例 10.24 使用端口传递虚接口的测试平台)

[XMR vs 端口传递虚接口对比](#XMR vs 端口传递虚接口对比)

虚接口数组使用流程

接口中的过程代码

并行协议接口

[例 10.25 含有使用并行协议任务的接口](#例 10.25 含有使用并行协议任务的接口)

串行协议接口

[例 10.26 含有使用串行协议任务的接口](#例 10.26 含有使用串行协议任务的接口)

接口代码的局限性

[物理接口 vs 虚拟接口 vs 接口中的代码](#物理接口 vs 虚拟接口 vs 接口中的代码)


在第 4 章中学习了如何使用接口连接设计和测试平台。这些物理接口代表了真实的信号,类似于 Verilog-1995 中跟端口相连的连线(wire)。测试平台通过端口(port)把这些接口静态地连接在一起。但是对于很多设计来讲,测试平台需要动态地连接到设计

例如在网络交换机中,DUT 的每一个输入通道都有一个接口,所以一个 Driver 类可能会连接到很多的接口。你大概不希望为每个通道都编写一个 Driver 类------相反可能希望编写一个通用的 Driver 类,将它例化 N 次,然后分别连接到 N 个物理端口。在 SystemVerilog 中,**虚接口(virtual interface)**是一个物理接口的句柄(handle),可以通过使用虚接口来做到这一点

可能需要编写一个对不同设计配置都可用的测试平台。例如,一个芯片可能有多种配置:芯片的管脚可能在一种配置中驱动了 USB 总线,在另一种配置中驱动了 I²C 串行总线。如果在测试平台中使用虚接口,就可以在运行测试平台时再决定使用哪个驱动器了

核心概念:虚接口是唯一可以桥接动态对象和静态模块、接口的一种机制

ATM路由器的虚接口

虚接口最常见的用法是允许测试平台中的对象使用一个通用的句柄,而非实际对象名来指向一个通过复制得到的接口中的数据项

只含有物理接口的测试平台

第 4 章描述了如何建立一个接口,并通过该接口将 4×4 的 ATM 路由器连接到测试平台

例 10.1 带有时钟块的 Rx 接口
复制代码
// 带有 modport 和时钟块的 Rx 接口
interface Rx_if(input logic clk);
    logic [7:0] data;
    logic soc, en, clav, rclk;

    clocking cb @(posedge clk);
        output data, soc, clav;    // 方向是相对于测试平台的
        input en;
    endclocking : cb

    modport TB(clocking cb);
    modport DUT(output en, rclk,
                input data, soc, clav);
endinterface : Rx_if
例 10.2 带有时钟块的 Tx 接口
复制代码
// 带有 modport 和时钟块的 Tx 接口
interface Tx_if(input logic clk);
    logic [7:0] data;
    logic soc, en, clav, tclk;

    clocking cb @(posedge clk);
        input data, soc, en;
        output clav;
    endclocking : cb

    modport TB(clocking cb);
    modport DUT(output data, soc, en, tclk,
                input clav);
endinterface : Tx_if
例 10.3 使用物理接口的测试平台(硬编码方式)
复制代码
program automatic test(Rx_if.TB Rx0, Rx1, Rx2, Rx3,
                       Tx_if.TB Tx0, Tx1, Tx2, Tx3,
                       input logic clk, output logic rst);

    bit [7:0] bytes[`ATM_SIZE];

    initial begin
        // 复位设备
        rst <= 1;
        Rx0.cb.data <= 0;
        // ...
        receive_cell0();
        // ...
    end

    task receive_cell0;
        @(Tx0.cb);
        Tx0.cb.clav <= 1;          // 给出开始接收的信号
        wait(Tx0.cb.soc == 1);     // 等待信元的开始
        for (int i = 0; i < `ATM_SIZE; i++) begin
            wait(Tx0.cb.en == 0);  // 等待使能
            @(Tx0.cb);
            bytes[i] = Tx0.cb.data;
            @(Tx0.cb);
            Tx0.cb.clav <= 0;      // 释放流控信号
        end
    endtask
endprogram
例 10.4 含有接口数组的顶层模块
复制代码
module top;
    logic clk, rst;

    Rx_if Rx[4](clk);
    Tx_if Tx[4](clk);

    test t1(Rx, Tx, rst);
    atm_router a1(Rx[0], Rx[1], Rx[2], Rx[3],
                  Tx[0], Tx[1], Tx[2], Tx[3],
                  clk, rst);

    initial begin
        clk = 0;
        forever #20 clk = !clk;
    end
endmodule : top

使用虚接口的测试平台

OOP 技术的一大特点是可以创建一个类,在类中使用句柄去指向对象,而非使用硬编码的对象名。这样一来,就可以创建一个 Driver 类和一个 Monitor 类,并通过句柄来操作数据,然后在运行时将数据操作传入该句柄

例 10.5 使用虚接口的测试平台
复制代码
program automatic test(Rx_if.TB Rx0, Rx1, Rx2, Rx3,
                       Tx_if.TB Tx0, Tx1, Tx2, Tx3,
                       output logic rst);

    Driver drv[4];
    Monitor mon[4];
    Scoreboard scb[4];

    virtual Rx_if.TB vRx[4] = '{Rx0, Rx1, Rx2, Rx3};
    virtual Tx_if.TB vTx[4] = '{Tx0, Tx1, Tx2, Tx3};

    initial begin
        foreach (scb[i]) begin
            scb[i] = new(i);
            drv[i] = new(scb[i].exp_mbx, i, vRx[i]);
            mon[i] = new(scb[i].rcv_mbx, i, vTx[i]);
        end
        // ...
    end
endprogram
例 10.6 使用接口数组的测试平台(更简洁)
复制代码
program automatic test(Rx_if.TB Rx[4], Tx_if.TB Tx[4],
                       output logic rst);
    // ...
    initial begin
        foreach (scb[i]) begin
            scb[i] = new(i);
            drv[i] = new(scb[i].exp_mbx, i, Rx[i]);
            mon[i] = new(scb[i].rcv_mbx, i, Tx[i]);
        end
        // ...
    end
endprogram
例 10.7 使用虚接口的驱动器类
复制代码
class Driver;
    int stream_id;
    bit done = 0;
    mailbox exp_mbx;
    virtual Rx_if.TB Rx;    // 虚接口句柄

    function new(input mailbox exp_mbx,
                 input int stream_id,
                 input virtual Rx_if.TB Rx);
        this.exp_mbx = exp_mbx;
        this.stream_id = stream_id;
        this.Rx = Rx;
    endfunction

    task run(input int ncells, input event driver_done);
        ATM_Cell ac;
        fork
            begin
                // 初始化输出信号
                Rx.cb.clav <= 0;
                Rx.cb.soc <= 0;
                @Rx.cb;

                // 驱动信元,直到发送最后一个信元
                repeat (ncells) begin
                    ac = new();
                    assert(ac.randomize());
                    if (ac.eot_cell) break;    // 传送结束
                    drive_cell(ac);
                end

                $display("@%0t: Driver::run Driver[%0d] is done",
                         $time, stream_id);
                ->driver_done;
            end
        join_none
    endtask : run

    task drive_cell(input ATM_Cell ac);
        bit [7:0] bytes[];
        #ac.delay;
        ac.byte_pack(bytes);

        $display("@%0t: Driver::drive_cell(%0d) vci=%h",
                 $time, stream_id, ac.vci);

        // 等待下一个时钟周期
        @Rx.cb;
        Rx.cb.clav <= 1;           // 置位 xfr
        do
            @Rx.cb;
        while (Rx.cb.en != 0);     // 等待使能信号变低

        Rx.cb.soc <= 1;            // 信元的开始
        Rx.cb.data <= bytes[0];    // 驱动第一个字节
        @Rx.cb;
        Rx.cb.soc <= 0;            // 信元传送完毕
        Rx.cb.data <= bytes[1];    // 驱动第二个字节

        for (int i = 2; i < `ATM_SIZE; i++) begin
            @Rx.cb;
            Rx.cb.data <= bytes[i];
        end

        @Rx.cb;
        Rx.cb.soc <= 1'bz;         // 结束时 SOC 置高阻
        Rx.cb.clav <= 0;
        Rx.cb.data <= 8'bz;        // 清除数据行

        $display("@%d: Driver::drive_cell(%d) finish",
                 $time, stream_id);

        // 将信元送至记分板
        exp_mbx.put(ac);
    endtask : drive_cell
endclass : Driver

将测试平台连接到端口列表中的接口

例 10.8 使用端口列表中的接口的测试用具
复制代码
module top;
    bus_ifc bus();    // 例化接口
    test t1(bus);     // 通过端口列表传递给测试程序
    dut d1(bus);      // 通过端口列表传递给 DUT
endmodule
例 10.9 端口列表含有接口的测试程序
复制代码
program automatic test(bus_ifc bus);
    initial $display(bus.data);    // 使用接口信号
endprogram
例 10.10 端口列表中含有第二个接口的顶层模块
复制代码
module top;
    bus_ifc bus();    // 例化接口
    new_ifc newb();   // 再例化一个接口
    test t1(bus, newb);    // 使用两个接口的测试程序
    dut d1(bus, newb);     // 使用两个接口的 DUT
    // ...
endmodule
例 10.11 端口中含有两个接口的测试程序
复制代码
program automatic test(bus_ifc bus, new_ifc newb);
    initial $display(bus.data);    // 使用接口信号
    $display(newb.addr);           // 新接口的信号
endprogram

问题 :在设计中增加一个新的接口意味着需要编辑已有的全部测试程序,这样新增的接口才能插入到测试用具中。怎样才能避免这种额外的工作量呢?可以通过避免使用端口连接来实现!

使用 XMR(跨模块引用)连接接口和测试程序

如果你的测试程序需要连接到测试用具中的物理接口上,那么可以使用程序块中的虚接口和跨模块引用(XMR,Cross Module Reference)

例 10.12 使用虚接口和 XMR 的测试程序
复制代码
program automatic test();
    virtual bus_ifc bus = top.bus;    // 跨模块引用
    initial $display(bus.data);       // 使用接口信号
endprogram
例 10.13 端口列表中不含接口的测试用具
复制代码
module top;
    bus_ifc bus();    // 例化接口
    test t1();        // 测试程序不使用端口列表
    dut d1(bus);      // DUT 仍旧使用端口列表
    // ...
endmodule
例 10.14 使用第二个接口的测试用具(测试程序无需改变)
复制代码
module top;
    bus_ifc bus();    // 例化接口
    new_ifc newb();   // 例化另一个
    test t1();        // 实例保持不变!
    dut d1(bus, newb);
    // ...
endmodule
例 10.15 使用两个虚接口和两个 XMR 的测试程序
复制代码
program automatic test();
    virtual bus_ifc bus = top.bus;
    virtual new_ifc newb = top.newb;

    initial begin
        $display(bus.data);     // 使用已有接口
        $display(newb.addr);    // 增加一个接口
    end
endprogram

物理接口 vs 虚接口 vs XMR 对比

方式 连接时机 灵活性 代码修改量
物理接口(端口) 编译时 新增接口需修改所有测试
虚接口 + XMR 运行时 新增接口无需修改已有测试

两种连接风格对比

复制代码
风格一:端口列表连接(传统)
┌─────────┐    端口     ┌─────────┐
│  测试   │─────────────│  顶层    │
│ 程序块  │             │  模块    │
└─────────┘             └────┬────┘
                             │ 端口
                             ▼
                        ┌─────────┐
                        │  DUT    │
                        └─────────┘
新增接口 → 需要修改测试程序

风格二:XMR 连接(推荐)
┌─────────┐   XMR      ┌─────────┐
│  测试   │─────────────│  顶层   │
│ 程序块  │  top.bus    │  模块   │
└─────────┘             └────┬────┘
                             │ 端口
                             ▼
                        ┌─────────┐
                        │  DUT    │
                        └─────────┘
新增接口 → 测试程序无需修改!

概念 说明
虚接口 物理接口的句柄,可动态连接到不同接口
虚接口数组 允许一组接口通过数组索引访问
XMR(跨模块引用) 从程序块中引用顶层模块中的接口
端口列表避免原则 使用 XMR 避免新增接口时修改所有测试
VMM 推荐风格 测试程序不使用端口列表,使用虚接口 + XMR

建议 :验证方法学上的一些规则会使得测试程序和测试用具比传统上使用端口的连接更复杂一些,但是这样做却意味着即使设计有所变化,也无需修改现有的测试程序的代码。如果你觉得测试程序的可重用性更加重要,那就有必要改变编码风格

连接到多个不同的设计配置

验证一个设计时,常见的挑战来自于该设计可能存在数个配置。你可以为每个配置单独编写一个测试平台,但是如果考虑到所有的可能性,那么各种组合的规模也许大得难以想象。而使用虚接口则使你可以动态地连接到各种可能的接口

网格(Mesh)设计案例

例 10.16 构造了一个可复制的简单器件------一个 8 位计数器。它跟网格配置中含有多个网络芯片或处理器实例的 DUT 非常相似。这个设计的关键是在顶层网单中建立一个接口和计数器的数组。这样测试平台就可以将这个虚拟接口数组连接到实际的物理接口上

例 10.16 8 位计数器的接口
复制代码
interface X_if(input logic clk);
    logic [7:0] din, dout;
    logic reset_l, load;

    clocking cb @(posedge clk);
        output din, load;
        input dout;
    endclocking

    always @cb
        $strobe("@%0t:%m: out=%0d, in=%0d, ld=%0d, r=%0d",
                $time, dout, din, load, reset_l);

    modport DUT(input clk, din, reset_l, load,
                output dout);
    modport TB(clocking cb, output reset_l);
endinterface
例 10.17 使用 X_if 接口的计数器模型
复制代码
// 带有装载和低电平复位输入的 8 位计数器
module dut(X_if.DUT xi);
    logic [7:0] count;
    assign xi.dout = count;

    always @(posedge xi.clk or negedge xi.reset_l) begin
        if (!xi.reset_l)
            count <= 0;
        else if (xi.load)
            count <= xi.din;
        else
            count <= count + 1;
    end
endmodule
例 10.18 使用虚接口数组的测试平台顶层模块
复制代码
parameter NUM_VI = 2;    // 设计实例的个数

module top;
    // 时钟发生器
    bit clk;
    initial begin
        clk <= '0;
        forever #20 clk <= ~clk;
    end

    // 例化 N 个接口
    X_if xi[NUM_VI](clk);

    // 例化测试平台
    test tb();

    // 产生 N 个 DUT 实例
    generate
        for (genvar i = 0; i < NUM_VI; i++) begin : dut_plk
            dut d(xi[i]);
        end
    endgenerate
endmodule : top
例 10.19 使用虚接口的计数器测试平台
复制代码
program automatic test;
    virtual X_if.TB vxi[NUM_XI];    // 虚接口数组
    Driver driver[];

    initial begin
        // 将局部虚接口连接到顶层
        vxi = top.xi;

        // 创建 N 个驱动器对象
        driver = new[NUM_XI];
        foreach (driver[i])
            driver[i] = new(vxi[i], i);

        foreach (driver[i]) begin
            int j = i;
            fork
                begin
                    driver[j].reset();
                    driver[j].load_op();
                end
            join_none
        end

        repeat (10) @(vxi[0].cb);
    end
endprogram
例 10.20 使用虚接口的 Driver 类
复制代码
class Driver;
    virtual X_if xi;
    int id;

    function new(input virtual X_if.TB xi, input int id);
        this.xi = xi;
        this.id = id;
    endfunction

    task reset();
        $display("@%0t:%m: Start reset[%0d]", $time, id);
        // 设备复位
        xi.reset_l <= 1;
        xi.cb.load <= 0;
        xi.cb.din <= 0;
        @(xi.cb) xi.reset_l <= 0;
        @(xi.cb) xi.reset_l <= 1;
        $display("@%0t:%m: End reset[%0d]", $time, id);
    endtask : reset

    task load_op();
        $display("@%0t:%m: Start load[%0d]", $time, id);
        ##1 xi.cb.load <= 1;
        xi.cb.din <= id + 10;

        ##1 xi.cb.load <= 0;
        repeat (5) @(xi.cb);
        $display("@%0t:%m: End load[%0d]", $time, id);
    endtask : load_op
endclass : Driver

对虚接口使用 typedef

virtual X_if.TB 可以使用 typedef 代替,这样可以保证你总是使用正确的 modport 并减少代码输入量

例 10.21 对虚接口使用 typedef 的测试平台
复制代码
typedef virtual X_if.TB vx_if;

program automatic test;
    vx_if vxi[NUM_XI];    // 虚接口数组
    Driver driver[];
    // ...
endprogram
例 10.22 在驱动器类型使用 typedef 的虚接口
复制代码
class Driver;
    vx_if xi;
    int id;

    function new(input vx_if xi, input int id);
        this.xi = xi;
        this.id = id;
    endfunction
    // ...
endclass : Driver

使用端口传递虚接口数组

前面的例子使用的是跨模块引用(XMR)来传递虚接口数组。虚接口数组传递的另一种方法是使用端口。因为在顶层网单中的数组是静态的,所以只需要引用一次,因此使用 XMR 风格比使用端口显得更加有意义,因为端口通常是用来传递不断变化的数值的

例 10.23 使用虚接口数组的顶层模块(端口方式)
复制代码
parameter NUM_XI = 2;    // 实例个数

module top;
    bit clk;
    // 例化 N 个接口
    X_if xi[NUM_XI](clk);
    // ...
    // 例化测试平台(通过端口传递接口数组)
    test tb(xi);
endmodule : top
例 10.24 使用端口传递虚接口的测试平台
复制代码
program automatic test(X_if xi[NUM_XI]);
    Driver driver[];
    virtual X_if vxi[NUM_XI];

    initial begin
        // 将局部虚接口连到顶层
        if (NUM_XI <= 0) $finish;

        driver = new[NUM_XI];
        vxi = xi;    // 把接口数组赋值给本地虚接口数组

        for (int i = 0; i < NUM_XI; i++) begin
            driver[i] = new(vxi[i], i);
            driver[i].reset();
        end
        // ...
    end
endprogram

XMR vs 端口传递虚接口对比

方式 优点 缺点
XMR(跨模块引用) 测试程序无需端口列表,新增接口不修改测试 隐式依赖顶层结构
端口传递 显式连接,结构清晰 新增接口需修改测试程序端口列表

虚接口数组使用流程

复制代码
│ 1. 顶层模块例化物理接口数组                                  
│    X_if xi[NUM_XI](clk);                                   
│                                                             
│ 2. 使用 generate 例化多个 DUT 实例                          
│    for (i=0; i<NUM_XI; i++) dut d(xi[i]);                  
│                                                             
│ 3. 测试程序定义虚接口数组                                    
│    virtual X_if.TB vxi[NUM_XI];                            
│                                                             
│ 4. 连接虚接口到物理接口(XMR 或端口)                       
│    vxi = top.xi;   // XMR 方式                             
│                                                             
│ 5. 创建多个驱动器实例,每个连接一个虚接口                    
│    driver[i] = new(vxi[i], i);                             

概念 说明
虚接口数组 允许一组接口通过数组索引动态访问
typedef 虚接口 减少代码输入量,确保 modport 一致
XMR 方式 推荐用于连接虚接口,避免修改已有测试
端口方式 显式连接,结构更清晰
generate 语句 批量例化多个 DUT 实例

接口中的过程代码

如同类中同时包含有变量和子程序,接口也可以包含子程序、断言、initialalways 块等代码。接口包括了两个块之间进行通信的信号和功能,所以总线的接口块可以包含信号和执行读写等指令的子程序。这些子程序的内部细节对外部是隐藏的,这就允许你稍后再编写实际的代码

对这些子程序的访问是通过 modport 语句来实现的,这一点跟信号一样。任务或者函数可以引入到 modport 中,它们对任何使用这个 modport 的块都是可见的

优势:这些子程序既可以被设计使用,也可以被测试平台所使用。这种实现方式保证了两者使用相同的协议,消除了某些常见的测试平台漏洞。

注意:并不是所有的综合工具都可以处理断言中的子程序代码

并行协议接口

创建系统的时候,你可能还不知道该选择并行还是串行的协议。例 10.25 中的接口含有 initiatorSendtargetRcv,这两个任务使用接口信号在两个块之间发送事务。这个接口通过两个 8 位总线并行地发送地址和数据

例 10.25 含有使用并行协议任务的接口
复制代码
interface simple_if(input logic clk);
    logic [7:0] addr;
    logic [7:0] data;
    bus_cmd_e cmd;

    modport TARGET
        (input addr, cmd, data,
         import task targetRcv(output bus_cmd_e c,
                               logic [7:0] a, d));

    modport INITIATOR
        (output addr, cmd, data,
         import task initiatorSend(input bus_cmd_e c,
                                   logic [7:0] a, d));

    // 并行发送
    task initiatorSend(input bus_cmd_e c,
                       logic [7:0] a, d);
        @(posedge clk);
        cmd <= c;
        addr <= a;
        data <= d;
    endtask

    // 并行接收
    task targetRcv(output bus_cmd_e c, logic [7:0] a, d);
        @(posedge clk);
        a = addr;
        d = data;
        c = cmd;
    endtask
endinterface : simple_if

串行协议接口

例 10.26 中的接口实现了地址和数据的串行收发。它和例 10.25 具有相同的接口和子程序,所以可以任意替换这两种接口,而不用对设计和测试平台的代码做任何修改

例 10.26 含有使用串行协议任务的接口
复制代码
interface simple_if(input logic clk);
    logic addr;
    logic data;
    logic start = 0;
    bus_cmd_e cmd;

    modport TARGET(input addr, cmd, data,
                   import task targetRcv(output bus_cmd_e c,
                                         logic [7:0] a, d));

    modport INITIATOR(output addr, cmd, data,
                      import task initiatorSend(input bus_cmd_e c,
                                                logic [7:0] a, d));

    // 串行发送
    task initiatorSend(input bus_cmd_e c, logic [7:0] a, d);
        @(posedge clk);
        start <= 1;
        cmd <= c;
        foreach (a[i]) begin
            addr <= a[i];
            data <= d[i];
            @(posedge clk);
            start <= 0;
        end
        cmd <= IDLE;
    endtask

    // 串行接收
    task targetRcv(output bus_cmd_e c, logic [7:0] a, d);
        @(posedge start);
        c = cmd;
        foreach (a[i]) begin
            @(posedge clk);
            a[i] = addr;
            d[i] = data;
        end
    endtask
endinterface : simple_if

接口代码的局限性

接口中的任务对 RTL 来讲是可以接受的,因为它们的功能是严格定义的。但是这些任务对任何验证 IP 来讲都不是一个高明的选择

局限性 说明
不能被扩展或重载 无法通过继承增加新功能
不能动态配置 不能根据配置动态地固化
不能含有私有数据成员 所有数据对外可见
不能作为类使用 缺少 OOP 的封装和多态特性

建议 :任何用于验证的代码都需要最大的灵活性和可配置性,所以应该把它们定义在程序块里运行的


SystemVerilog 中的接口是一种功能强大的技术,它整合了连接、时序和块之间的通信功能

要点 说明
虚拟接口 物理接口的句柄,可在运行时连接
动态连接 使用虚接口数组连接到多个物理接口
接口中的子程序 可包含驱动信号的子程序和检查协议的断言
代码可替换 并行/串行协议可无缝替换
测试平台位置 测试程序应该放在程序块而不是接口中
物理接口 vs 虚拟接口 vs 接口中的代码
特性 物理接口 虚拟接口 接口中的代码
连接时机 编译时 运行时 编译时
可动态配置
可扩展/重载
可包含子程序 ---
适用场景 静态连接 动态连接 协议封装

接口在很多方面跟含有指针、封装和抽象的类很相似。这使你可以通过创建接口在比传统 Verilog 的端口和连接更高的层次上对系统进行建模,但是一定要记得将测试平台放到程序块中去

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    测试平台(程序块)                        │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    类(驱动器、监视器等)             │   │
│  │                   使用虚接口句柄                     │   │
│  └─────────────────────────────────────────────────────┘   │
│                           │                                │
│                           ▼                                │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              物理接口(interface)                   │   │
│  │    ┌─────────────────────────────────────────┐      │   │
│  │    │  信号声明  │  子程序  │  断言  │  时钟块 │       │   │
│  │    └─────────────────────────────────────────┘      │   │
│  └─────────────────────────────────────────────────────┘   │
│                           │                                │
│                           ▼                                │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                      DUT                            │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
主题 核心内容
10.1 ATM 路由器的虚接口 虚接口数组、XMR、驱动器类
10.1.3 端口列表连接 传统方式,新增接口需修改测试
10.1.4 XMR 连接 推荐方式,新增接口无需修改测试
10.2 多个设计配置 网格设计、接口数组、typedef
10.3 接口中的过程代码 并行/串行协议任务、局限性