• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

SOC/IP验证工程师

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

systemverilog中clocking的用法

Clocking块在SystemVerilog中主要用于在测试平台(testbench)中同步和采样信号,特别是在接口(interface)中定义。它有助于减少竞争条件,并使得测试平台更加可读和可维护。下面我将详细介绍clocking块的用法。

1. 基本语法
systemverilog
clocking <clocking_name> @(<event>);
    default input #<delay> output #<delay>;
    <direction> <signal1>;
    <direction> <signal2>;
    // ...
endclocking
<clocking_name>:clocking块的名称。

@(<event>):通常是一个时钟事件,如posedge clk或negedge clk。

default语句:指定默认的输入和输出延迟。如果没有指定,则默认输入在时钟事件之前1step采样,输出在时钟事件之后0时间驱动。

<direction>:可以是input、output或inout。注意,在clocking块中,inout通常不被推荐,因为时钟块主要用于同步驱动和采样。

<signal>:可以是接口中的信号,或者通过<clocking_block_name>.<signal>来访问。

2. 主要作用
同步采样和驱动:确保在时钟事件发生时采样输入信号,并在指定的延迟后驱动输出信号。

减少竞争条件:通过指定输入采样和输出驱动的延迟,可以避免设计中的竞争条件。

提供时序抽象:测试平台可以在事务级操作,而不需要关心具体的时钟延迟。

3. 示例
假设我们有一个接口,包含时钟clk、输入信号data和输出信号valid。

systemverilog
interface my_interface(input bit clk);
    logic [7:0] data;
    logic valid;

    // 定义一个clocking块,基于clk的上升沿
    clocking cb @(posedge clk);
        default input #1step output #0;
        input data;
        input valid;
        // 注意:通常clocking块中不定义输出方向,因为输出通常由驱动端控制,但也可以定义输出
        // 这里我们只定义输入,用于采样
    endclocking

    // 也可以定义另一个clocking块用于驱动输出
    clocking drv_cb @(posedge clk);
        default input #1step output #2;
        output data;
        output valid;
    endclocking

    // 注意:同一个信号不能在同一个clocking块中同时定义为input和output
endinterface
在测试平台中,可以使用clocking块来同步采样和驱动:

systemverilog
module testbench;
    bit clk;
    my_interface intf(clk);

    always #5 clk = ~clk;

    initial begin
        // 使用clocking块采样
        @(intf.cb);
        $display("data = %h, valid = %b", intf.cb.data, intf.cb.valid);

        // 使用clocking块驱动
        intf.drv_cb.data <= 8'hFF;
        intf.drv_cb.valid <= 1;

        // 等待下一个时钟周期
        @(intf.cb);
        // 再次采样
        $display("data = %h, valid = %b", intf.cb.data, intf.cb.valid);
    end
endmodule
4. 延迟说明
input #<delay>:指定在时钟事件发生前<delay>时间采样输入信号。#1step表示在时钟事件前1个时间步长采样,这可以避免竞争条件。

output #<delay>:指定在时钟事件发生后<delay>时间驱动输出信号。例如,#2表示在时钟事件后2个时间单位驱动。

5. 使用clocking块的注意事项
Clocking块通常定义在接口中,但也可以定义在模块和程序中。

在同一个clocking块中,不能将同一个信号同时定义为输入和输出。如果有需要,可以定义两个不同的clocking块,一个用于采样,一个用于驱动。

在测试平台中,通过clocking块访问信号时,使用<clocking_block_name>.<signal>的格式。

Clocking块主要用于测试平台,而不是可综合的设计代码。

6. 在程序(program)中使用clocking块
SystemVerilog引入了程序(program)构造,用于将测试平台与设计分离。在程序中,可以使用clocking块来同步测试平台的操作。

systemverilog
program test_program(my_interface intf);
    // 使用接口中的clocking块
    initial begin
        // 等待时钟事件
        @(intf.cb);
        // 采样信号
        $display("data = %h", intf.cb.data);
    end
endprogram
7. 默认clocking
可以指定一个默认的clocking块,这样在等待时钟事件时可以直接使用@(cb)而不需要指定具体的clocking块。

systemverilog
interface my_interface(input bit clk);
    clocking cb @(posedge clk);
        // ...
    endclocking

    // 指定默认clocking
    default clocking cb;
endinterface
在测试平台中,可以这样使用:

systemverilog
module testbench;
    my_interface intf(clk);
    // 由于在接口中指定了默认clocking,我们可以直接使用
    initial begin
        // 等待默认clocking的时钟事件
        @(intf.cb); // 或者直接写 @(intf) 也可以,因为指定了默认clocking
    end
endmodule
8. 总结
Clocking块是SystemVerilog中用于测试平台同步的重要构造,它通过定义时钟事件和延迟,使得信号采样和驱动更加可控,减少了竞争条件。在接口中定义clocking块,可以使测试平台更加模块化和可重用

SystemVerilog中的clocking块是用于同步和采样信号的重要构造,主要用于验证环境(testbench)中。它可以简化时钟域信号的时序管理,避免竞争条件。

基本语法

clocking clocking_name @(clock_event);
    default input #setup_time output #hold_time;
    input signal_name;
    output signal_name;
    inout signal_name;
endclocking

主要用途

1. 定义信号采样和驱动时序

interface bus_if(input bit clk);
    logic [7:0] data;
    logic valid;
    logic ready;
    
    // 定义clocking块
    clocking cb @(posedge clk);
        // 默认时序:输入在时钟沿前1ns采样,输出在时钟沿后2ns驱动
        default input #1ns output #2ns;
        
        // 具体信号的时序(可覆盖默认值)
        input #0 data;      // 在时钟沿采样
        input valid;        // 使用默认的#1ns
        output #1 ready;    // 在时钟沿后1ns驱动
    endclocking
endinterface

2. 在测试平台中使用

module testbench;
    bit clk;
    logic [7:0] data;
    logic valid;
    
    // 实例化接口
    bus_if bus(.clk(clk));
    
    // 生成时钟
    always #5 clk = ~clk;
    
    initial begin
        // 等待时钟事件
        @(bus.cb);
        
        // 采样输入信号(在时钟沿前1ns)
        $display("Sampled data: %0h", bus.cb.data);
        
        // 驱动输出信号(在时钟沿后2ns)
        bus.cb.ready <= 1;
        
        // 同步等待
        repeat(3) @(bus.cb);
        
        // 使用clocking事件作为时序控制
        @(bus.cb iff bus.cb.valid);
    end
endmodule

关键特性

1. 时序控制

clocking cb @(posedge clk);
    // 不同时序规格
    input  #1step signal1;  // 在前一个时间步采样
    input  #0     signal2;  // 在时钟沿采样
    input         signal3;  // 使用默认时序
    output #2     signal4;  // 时钟沿后2ns驱动
    output #1step signal5;  // 前一时间步驱动
endclocking

2. 跨模块使用

// 在interface中定义
interface mem_if(input bit clk);
    logic [31:0] addr;
    logic [31:0] data;
    logic wr_en;
    
    clocking drv_cb @(posedge clk);
        default input #1 output #2;
        output addr, data, wr_en;
    endclocking
    
    clocking mon_cb @(posedge clk);
        default input #1;
        input addr, data, wr_en;
    endclocking
endinterface

// 在测试中使用不同视角
module test;
    mem_if mem_intf(clk);
    
    // 驱动端视角
    initial begin
        mem_intf.drv_cb.addr <= 32'h1000;
        mem_intf.drv_cb.data <= 32'hABCD;
    end
    
    // 监控端视角
    initial begin
        forever begin
            @(mem_intf.mon_cb);
            $display("Monitor: addr=%h", mem_intf.mon_cb.addr);
        end
    end
endmodule

3. 与program块结合

program test_program(bus_if bus);
    initial begin
        // 自动使用clocking块的时序
        bus.cb.data <= 8'hFF;
        
        // ##延迟操作符(clocking cycle delay)
        ##2;  // 等待2个时钟周期
        
        // 使用clocking事件
        @(bus.cb);
        
        // 条件等待
        @(bus.cb iff bus.cb.valid == 1);
        
        // 采样
        automatic logic [7:0] sampled = bus.cb.data;
    end
endprogram

常用技巧

1. 避免竞争条件

clocking safe_cb @(posedge clk);
    // 输入在时钟沿前采样,避免建立时间违规
    default input #1step;
    
    // 输出在时钟沿后驱动,避免保持时间违规
    default output #0;
endclocking

2. 多时钟域处理

interface multi_clk_if(input bit clk1, input bit clk2);
    clocking cb1 @(posedge clk1);
        input  data1;
        output ctrl1;
    endclocking
    
    clocking cb2 @(posedge clk2);
        input  data2;
        output ctrl2;
    endclocking
endinterface

3. 在类中使用

class driver;
    virtual bus_if vif;
    
    task run();
        forever begin
            @(vif.cb);  // 同步到时钟
            
            // 使用clocking驱动信号
            vif.cb.addr <= new_addr;
            vif.cb.wr_en <= 1;
        end
    endtask
endclass

实际示例

// 完整的接口示例
interface axi_if(input bit clk, input bit rst_n);
    // 信号声明
    logic [31:0] awaddr;
    logic awvalid;
    logic awready;
    
    // 驱动clocking块
    clocking drv_cb @(posedge clk);
        default input #1 output #2;
        output awaddr, awvalid;
        input  awready;
    endclocking
    
    // 监控clocking块
    clocking mon_cb @(posedge clk);
        default input #1;
        input awaddr, awvalid, awready;
    endclocking
    
    // 同步复位
    modport driver(clocking drv_cb, input rst_n);
    modport monitor(clocking mon_cb, input rst_n);
endinterface

// 使用示例
module tb;
    bit clk, rst_n;
    axi_if axi_intf(clk, rst_n);
    
    // 时钟生成
    always #5 clk = ~clk;
    
    initial begin
        rst_n = 0;
        #100 rst_n = 1;
        
        // 使用clocking块同步操作
        @(axi_intf.drv_cb);
        axi_intf.drv_cb.awaddr <= 32'h4000_0000;
        axi_intf.drv_cb.awvalid <= 1;
        
        // 等待握手
        @(axi_intf.drv_cb iff axi_intf.drv_cb.awready);
        
        // 清除信号
        axi_intf.drv_cb.awvalid <= 0;
    end
endmodule

注意事项

  1. clocking块只能在interface、module、program或checker中声明
  2. 不可综合,仅用于验证
  3. input表示采样,output表示驱动
  4. #1step是特殊的延时,表示前一个仿真时间步
  5. 建议在interface中定义clocking,提高重用性

clocking块是构建健壮验证环境的关键工具,它能有效管理时序并减少竞争条件。

posted on 2026-01-23 23:28  SOC验证工程师  阅读(9)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3