SV系统验证—第四课:接口&采样驱动&测试始末

目录

1. 接口

1.1 接口的概念

1.2 接口的定义和使用 

2. 采样和数据驱动

2.1 竞争问题

2.2 接口中的clocking(时钟块) 

3. 测试的开始和结束

1. 接口

1.1 接口的概念

 Arbiter

Testbench 

Top 

1.2 接口的定义和使用 

在硬件描述语言(如SystemVerilog)中,`module` 和 `interface` 是两个核心概念,它们用于定义数字系统的行为和结构。下面详细解释了它们的区别与联系:

### Module(模块)

- **定义**:模块是SystemVerilog中的基本构建块,用来封装设计的某一部分,包括输入、输出端口、内部信号以及实现逻辑。
- **功能**:
  - 模块可以包含寄存器、线网声明、过程块(initial和always)、连续赋值语句(assign),以及其他模块实例化。
  - 它们可以被看作是硬件设计中的一个独立单元,可以进行单独的设计、测试和重用。
- **使用场景**:适用于定义任何需要实现的具体逻辑或功能块。

### Interface

- **定义**:接口是一种特殊的结构,用于抽象和简化复杂通信协议的定义,通常用于规定模块之间的交互方式。它允许将一组相关的信号组织在一起,形成一种高层次的通信契约。
- **功能**:
  - 接口可以包含时钟、复位信号、数据信号等,并且能够定义任务(task)、函数(function)、modport(用于定义不同模块间如何使用这些信号)等。
  - 通过使用接口,可以减少重复代码,提高可维护性,并使设计更加清晰。
- **使用场景**:当多个模块需要遵循相同的通信协议进行交互时非常有用,比如总线协议、流控制等。

### 区别与联系

- **区别**:
  - 模块关注于具体的功能实现,而接口更侧重于定义模块之间如何交换信息。
  - 模块可以直接实例化并相互连接来构成完整的系统;相比之下,接口提供了一种标准化的方式来描述这种连接关系。
  
- **联系**:
  - 在实际应用中,接口经常与模块一起使用。例如,在模块声明中可以通过接口来指定其端口列表,从而使得模块间的连接更加直观和易于管理。
  - 接口可以被视为一种高级的抽象层,它位于模块之上,帮助管理和规范模块之间的通信规则。

总之,`module` 和 `interface` 在SystemVerilog中各有其独特的角色和用途。合理地结合使用两者可以帮助开发者创建出既高效又易于理解和维护的硬件设计。

例子1

// 接口示例 - 展示SystemVerilog中interface的使用

// 声明membus接口
interface membus (
    input logic clk    // 时钟信号,作为接口的输入端口
);  // 接口的端口声明与module类似,用于顶层连接

    // 声明用于内部模块例化的连接信号
    logic        mrdy;     // 内存就绪信号(Memory Ready)
    logic        wen;      // 写使能(Write Enable)
    logic        ren;      // 读使能(Read Enable)
    logic [7:0]  addr;     // 8位地址总线
    logic [7:0]  c2m_data; // 主设备到从设备的数据(CPU to Memory)
    logic [7:0]  m2c_data; // 从设备到主设备的数据(Memory to CPU)
    wor          status;   // 状态信号(线或类型,多个驱动源会产生线或结构)

    // 从设备使用的任务:响应读请求
    task reply_read (
        input logic [7:0] data,  // 要返回的数据
        integer delay            // 响应延迟时间
    );
        #delay;                  // 等待指定的延迟时间
        @(negedge clk)           // 在时钟下降沿
        mrdy = 1'b0;             // 拉低就绪信号
        m2c_data = data;        // 从设备回复数据
        @(negedge clk)          // 下一个时钟下降沿
        mrdy = 1'b1;            // 恢复就绪信号
    endtask

    // 主设备使用的任务:读取内存
    task read_memory (
        input logic [7:0] raddr,  // 要读取的地址
        output logic [7:0] data    // 读取到的数据
    );
        @(posedge clk);           // 等待时钟上升沿
        ren = 1'b0;               // 拉低读使能
        addr = raddr;             // 设置地址总线
        @(negedge mrdy);         // 等待从设备响应(mrdy变低)
        @(posedge clk);          // 等待时钟上升沿
        data = m2c_data;         // 获取从设备返回的数据
        ren = 1'b1;              // 恢复读使能
    endtask

    // 定义主设备接口视图(modport)
    modport master (
        output wen, ren, addr, c2m_data, status,  // 主设备输出信号
        input mrdy, m2c_data,                      // 主设备输入信号
        import read_memory                         // 导入主设备可用的任务
    );

    // 定义从设备接口视图(modport)
    modport slave (
        input wen, ren, addr, c2m_data, status,    // 从设备输入信号
        output mrdy, m2c_data,                     // 从设备输出信号
        import reply_read                          // 导入从设备可用的任务
    );

endinterface

/* *************** 内存核心模块 *************** */
module mem_core (
    membus.slave mb  // 使用slave接口视图
);
    logic [7:0] mem [255:0];  // 256字节内存数组

    // 初始化内存,每个地址存储其索引值
    initial begin
        for (int i = 0; i < 256; i++)
            mem[i] = i;
    end

    // 驱动status信号为0
    assign mb.status = 1'b0;

    // 当检测到读使能下降沿时,调用reply_read任务返回数据
    always @(negedge mb.ren)
        mb.reply_read(mem[mb.addr], 100);  // 返回对应地址的数据,延迟100个时间单位
endmodule

/* *************** CPU核心模块 *************** */
module cpu_core (
    membus.master mb  // 使用master接口视图
);
    // 驱动status信号为0
    assign mb.status = 1'b0;

    initial begin
        logic [7:0] read_data;  // 存储读取结果的变量
        
        // 调用read_memory任务读取地址16(0x10)的数据
        mb.read_memory(8'b0001_0000, read_data);
        
        // 显示读取结果
        $display("Read Result at time %t: data = %h", $time, read_data);
    end
endmodule

/* *************** 顶层模块 *************** */
module top ();
    wor    status;  // 线或类型的状态信号(实际在接口中使用)
    logic  clk = 1'b0;  // 时钟信号,初始为0

    // 实例化接口,传入时钟信号
    membus mb(clk);

    // 实例化内存模块(两种写法等效)
    // mem_core mem(mb.slave);  // 显式指定slave视图
    mem_core mem(mb);          // 隐式使用slave视图(因为mem_core声明为slave)

    // 实例化CPU模块,指定master视图
    cpu_core cpu(mb.master);

    // 生成时钟信号(256个周期)
    initial begin
        for (int i = 0; i <= 255; i++)
            #1 clk = ~clk;  // 每隔1个时间单位翻转时钟
    end
endmodule

例子2

`timescale 1ns / 1ns

// 定义接口 `if_port`,包含一个时钟信号 `clk`
interface if_port (input bit clk);

    // 声明接口中使用的信号线:两个加数 a 和 b、进位输入 cin、和 sum、进位输出 cout
    logic a, b, cin, sum, cout;

    // 定义 clocking block `cp`:在时钟上升沿操作,用于驱动输出信号
    clocking cp @ (posedge clk);
        output a, b, cin;   // 这些信号由激励模块(simulus)驱动
    endclocking

    // 定义另一个 clocking block `cn`:在时钟下降沿操作,用于采样输入信号
    clocking cn @ (negedge clk);
        input a, b, cin, sum, cout;  // 这些信号由加法器模块提供,monitor 模块采样
    endclocking

    // 定义接口的 modport:
    // - `stimulus` 使用 clocking cp 来驱动信号
    // - `adder` 接收输入 a, b, cin,并输出 sum, cout
    // - `monitor` 使用 clocking cn 来采样信号
    modport stimulus (clocking cp);
    modport adder (input a, b, cin, output cout, sum);
    modport monitor (clocking cn);

endinterface : if_port


// 激励模块 simulus,使用接口的 stimulus modport
module simulus (if_port.stimulus port);

    // 在每个时钟上升沿触发,为 a、b、cin 赋随机值(0 或 1)
    always @(port.cp) begin
        port.cp.a   <= $random() % 2;
        port.cp.b   <= $random() % 2;
        port.cp.cin <= $random() % 2;
    end

endmodule : simulus


// 加法器模块 adder,使用接口的 adder modport
module adder (if_port.adder port);

    // 计算一位全加器的结果:a + b + cin,结果赋给 {cout, sum}
    assign {port.cout, port.sum} = port.a + port.b + port.cin;

endmodule : adder


// 监控模块 monitor,使用接口的 monitor modport
module monitor (if_port.monitor mon);

    // 在每个时钟下降沿触发,打印当前输入输出的值以及当前仿真时间
    always @(mon.cn) begin
        $display("%d + %d + %d = %d %d at time %0t", 
            mon.cn.a, mon.cn.b, mon.cn.cin, mon.cn.cout, mon.cn.sum, $time);
    end

endmodule : monitor


// 顶层模块 top
module top ();

    // 设置时间单位和精度为 1 ns
    timeunit      1ns;
    timeprecision 1ns;

    // 定义时钟信号 clk,初始值为 0
    bit clk = 0;

    // 实例化接口 if_port,并连接到 clk
    if_port port (clk);

    // 实例化三个模块,并通过接口的不同 modport 进行连接
    simulus sim (port.stimulus);  // 激励模块
    adder   add (port.adder);     // 加法器模块
    monitor mon (port.monitor);   // 监控模块

    // 产生时钟信号:每 10 ns 翻转一次,即周期为 20 ns,频率为 50 MHz
    always #10 clk = ~clk;

endmodule : top

1.3 虚接口

虚接口(`virtual interface`)是 SystemVerilog 中的一种机制,它允许在面向对象编程(OOP)的环境中引用硬件设计中的接口。通过使用虚接口,你可以将测试平台的设计与具体的硬件接口解耦,这为验证环境提供了更大的灵活性和可重用性。

虚接口的主要用途

  • 解耦设计和验证环境
  • 支持面向对象编程
  • 提高模块化和重用性

使用方法

  • 定义接口:首先需要定义一个接口,该接口描述了设计中的一组信号及其行为。
  • 声明虚接口:然后,在类中声明一个虚接口类型的成员变量。这个变量并不直接存储接口的数据,而是作为指向实际接口的一个“指针”。
  • 赋值给虚接口:通过某种方式(如构造函数或专门的方法),将实际的接口实例赋值给虚接口变量。
  • 使用虚接口:一旦虚接口被赋值,就可以像使用普通接口一样使用它,包括访问其内部的信号和时钟块。
```systemverilog
class chnl_initiator;
  ...
  virtual chnl_intf intf;         // 声明一个虚接口成员
  ...
  function void set_interface(virtual chnl_intf intf);
    if (intf == null)
      $error("Interface handle is NULL, please check if target interface has been instantiated");
    else
      this.intf = intf;           // 给虚接口赋值
  endfunction
  ...
endclass
```

这样做的好处是,`chnl_initiator` 类不再依赖于特定的接口实例,而是可以通过传递不同的接口实例来操作不同的硬件模块。这种做法极大地增强了测试平台的灵活性和可维护性。

2. 采样和数据驱动

2.1 竞争问题

2.2 接口中的clocking(时钟块) 

Clocking可以限定信号传输的方向 

例子1 

 

 例2

 

结论

3. 测试的开始和结束

例子

 

解答

故答案是AB

 Program和Module

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值