UVM知识点7-Clocking block在验证中的正确使用

本文介绍了Clocking blocks在UVM验证中的重要性,它可用于输入激励驱动和输出采样,能避免竞争冒险。文中说明了如何利用其构造理想环境,还给出使用示例,包括驱动和采样interface的代码,以及不同interface类型的写法,最后提及异步复位时的处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Clocking blocks在UVM的验证中使用非常重要,主要用于对输入的激励驱动和对输出的采样,但是很多人可能都不知道。

如下图所示,理想情况下,我们会在图中蓝线处进行驱动或者采样,从而尽可能避免竞争冒险。
在这里插入图片描述

SystemVerilog考虑到了这一点,可以参考寄存器时序逻辑运行的方式,即将所有的输出端都进行寄存来尽可能避免验证环境中的竞争冒险。因为毕竟建立时间和保持时间等时序方面的验证不该由DV来负责,也就是说说默认情况下,我们DV要假定该RTL将来综合后的电路在timing上没有问题,因此我们可以考虑利用Clocking blocks来构造一个理想的驱动和采样interface的环境。
在这里插入图片描述

上图所示,举个例子,一般情况下一个cycle中大部分,比如80%的时间用于输入端的组合逻辑的路径延迟,剩余的20%的cycle时间用于寄存和输出。

那么以上面为例,如何来使用Clocking blocks呢?

直接来看代码:

clocking cb1 @(posedge clk)
    default input #1step output (`CYCLE * 0.2);
    input <list of all inputs> ;
    output <list of all outputs>;
endclocking

在我们的env环境中,寄存输出的值即是我们要驱动到DUT interface上的激励,而env环境中的输入,则需要在interface上采样并且被传递到testbench中。

最佳采样DUT interface端数据的时间,是刚好在下一个时钟跳变沿改变输出端数据之前进行采样,这个时候应该是最稳定的时候,只是这里的input我们使用了#1step的延迟,即相当于仿真时间精度。

最佳驱动时间,应该是在时钟跳变沿之前的10%~20%,因为那个时候激励也最稳定,要在DUT采样interface之前预留一些时间。

下面来看个例子,首先看这里的interface。

interface dut_if (input clk);
    logic [15:0] dout;
    logic [15:0] din;
    logic ld, inc, rst_n;

    clocking cb1 @(posedge clk);
        default input #1step output `Tdrive;
        input dout;
        output din;
        output ld, inc, rst_n;
    endclocking
endinterface

驱动interface
下面来看如何在driver里将transaction驱动到interface上。

task drive_tr (trans1 tr);
@vif.cb1;
vif.cb1.din <= tr.din;
vif.cb1.ld <= tr.ld;
vif.cb1.inc <= tr.inc;
vif.cb1.rst_n <= tr.rst_n;
endtask
很简单,我们通过Clocking blocks来完成输出端的驱动。

通常我们在driver里驱动之前会对interface做一下初始化,比如这里一般会使用initialize方法。

如果是前序模块给的输出的话,那么需要置成对应的有效值,否则初始化为x不定态来模拟最真实的场景,注意这需要EDA工具提供进一步的支持。

class tb_driver extends uvm_driver #(trans1);
    `uvm_component_utils(tb_driver)
    virtual dut_if vif;

    function new (string name, uvm_component parent);
        super.new(name, parent);
    endfunction

    task run_phase(uvm_phase phase);
        trans1 tr;
        initialize();
        forever begin
        seq_item_port.get_next_item(tr);
        drive_tr(tr);
        seq_item_port.item_done();
        end
    endtask

    task initialize(); // @0 - Does not use clocking block
        vif.rst_n <= '0;
        vif.ld <= '1;
        vif.inc <= '1;
        vif.din <= 'dx;
    endtask

    task drive_tr (trans1 tr);
        @vif.cb1;
        vif.cb1.din <= tr.din;
        vif.cb1.ld <= tr.ld;
        vif.cb1.inc <= tr.inc;
        vif.cb1.rst_n <= tr.rst_n;
    endtask
endclass

监测interface
下面再来看如何采样interface上的信号。

class tb_monitor extends uvm_monitor;
    `uvm_component_utils(tb_monitor)
    virtual dut_if vif;
    uvm_analysis_port #(trans1) aport;

    function new (string name, uvm_component parent);
    super.new(name, parent);
    endfunction

    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        aport = new("aport", this); // build the analysis port
    endfunction

    task run_phase(uvm_phase phase);
        trans1 tr;
        tr = trans1::type_id::create("tr");
        //---------------------------------------
        forever begin
            sample_dut(tr);
            aport.write(tr);
        end
    endtask
    //-----------------------------------------------
    // sample_dut assumed to be synced to posedge clk
    // except for first sample at time-0
    //-----------------------------------------------
    task sample_dut (output trans1 tr);
        trans1 t = trans1::type_id::create("t");
        //---------------------------------------------
        // Sample DUT synchronous inputs on posedge clk.
        // DUT inputs should have been valid for most
        // of the previous clock cycle
        //---------------------------------------------
        t.din = vif.din;
        t.ld = vif.ld;
        t.inc = vif.inc;
        t.rst_n = vif.rst_n;
        //---------------------------------------------
        // Wait for posdege clk and sample outputs #1step before.
        // Also re-sample and check async control input signals
        //---------------------------------------------
        @vif.cb1;
        if (!vif.rst_n) t.rst_n = '0; // async reset
        t.dout = vif.cb1.dout;
        //---------------------------------------------
        tr = t;
    endtask
endclass

如果monitor的信号在clocking block是output方向的话(对应的即RTL的input方向),则使用t.xxx = vif.xxx的语句先monitor到interface上的信号值,然后等时钟沿@vif.cb1来进行同步,那么可以理解为在时钟上升沿到来之前,output方向的信号已经稳定(意味着RTL的输入端激励在时钟上升沿到来之前已经稳定),所以在时钟沿之前进行阻塞完成赋值。

然后对于input方向的信号dout(对应RTL的output方向),则使用cb1的三段式来赋值,即t.dout = vif.cb1.dout,主要是考虑RTL的output应该在时钟上升沿之后才会将有效的结果输出值驱动到interface上,因此这里需要等待1step的延迟之后通过阻塞赋值来monitor到RTL输出端驱动到interface上的稳定的值。

因此需要区分不同interface的类型,采用不同的写法。

但问题来了,如下图所示,如果这个rst_n是个异步复位且刚好在一个cycle之间进行了异步复位,那么如何monitor到该动作呢?
在这里插入图片描述

那么首先我们需要对interface做如下修改:

interface dut_if (input clk);
    logic [15:0] dout;
    logic [15:0] din;
    logic ld, inc, rst_n;
    logic reset_n;
    //----------------------------------------------------
    // Sticky reset_n signal to capture short rst_n pulses
    //----------------------------------------------------
    always_ff @(posedge clk, negedge rst_n)
        if (!rst_n) reset_n <= '0;
        else reset_n <= '1;
    clocking cb1 @(posedge clk);
        default input #1step output `Tdrive;
        input dout;
        output din;
        output ld, inc, rst_n;
        input reset_n;
    endclocking
endinterface

然后再来修改monitor。

class tb_monitor extends uvm_monitor;
    `uvm_component_utils(tb_monitor)
    ...
    task run_phase(uvm_phase phase);
        trans1 tr;
        tr = trans1::type_id::create("tr");
        //---------------------------------------
        forever begin
            sample_dut(tr);
            aport.write(tr);
        end
    endtask

    task sample_dut (output trans1 tr);
        trans1 t = trans1::type_id::create("t");
        t.din = vif.din;
        t.ld = vif.ld;
        t.inc = vif.inc;
        t.rst_n = vif.rst_n;
        //---------------------------------------------
        // ...
        // Sample the sticky-bit reset_n to update rst_n if needed
        //---------------------------------------------
        @vif.cb1;
        if (!vif.cb1.reset_n) t.rst_n = '0; // async reset
        t.dout = vif.cb1.dout;
        //---------------------------------------------
        tr = t;
    endtask
endclass

https://zhuanlan.zhihu.com/p/382248330

<think>我们正在讨论UVM中的interfaceclockingblock。根据引用[1][2][3][5],clockingblock在interface中定义,主要用于解决Testbench与DUT之间的时序同步问题,特别是在处理信号采样和驱动时避免竞争条件。**核心作用总结:**1.**同步信号采样与驱动**:通过定义时钟事件(如`@(posedgeclk)`)和延迟(`#1step`输入,`#2`输出),确保在正确的时刻采样DUT输出和驱动DUT输入[^2][^3]。2.**避免竞争条件**:默认的`input#1step`使采样发生在时钟边沿前(对应上一个时钟周期的稳态值),而`output#2`使驱动发生在时钟边沿后,避开建立/保持时间窗口[^2]。3.**简化时序控制**:在Testbench中直接通过`clocking_handle.signal`操作信号,自动遵循预定义的时序规则[^2][^5]。**关键特性详解:**-**输入采样时机**:`#1step`表示采样时钟沿前最后时刻的值(即上一个时钟周期的稳定值)[^2]。-**输出驱动时机**:`#2`表示时钟沿后2ns驱动,模拟真实寄存器延迟[^2]。-**默认时序规则**:在`clockingblock`中定义`defaultinput/output`可全局应用延迟[^2][^3]。**典型应用场景(引用[2]示例):**```systemveriloginterfacedata_if(inputclk);logic[7:0]data;logicvalid;//定义时钟块clockingcb@(posedgeclk);outputdata,valid;//Testbench驱动input#1stepdata;//在时钟沿前采样endclockingmodporttb(clockingcb);//暴露给Testbenchendinterface```>**Testbench操作**:通过`dif.cb.data<=value`驱动信号时,自动延迟2ns;通过`@(dif.cb)`等待时钟事件时,采样发生在时钟沿前[^2]。**与UVM组件的集成(引用[4][5]):**-UVMDriver通过`clockingblock`驱动DUT输入[^4]。-UVMMonitor通过`clockingblock`采样DUT输出,避免异步采样错误[^5]。**注意事项:**-在Monitor中若需多拍后采样,应使用`clocking_handle`的等待语法(如`@(vif.cb)`),而非直接`@(posedgeclk)`,否则可能违反时序规则[^5]。>**示例错误场景**:引用[5]中因未正确使用`clockingblock`导致采样到旧值,改用`@(vif.mon_mp.cb)`后解决[^5]。---###相关问题1.`defaultinput#1step`和`defaultinput#0`在采样行为上有何区别?2.如何在同一个interface中定义多个clockingblock以适应多时钟域?3.UVMDriver中直接操作interface信号与通过clockingblock操作有何风险差异?</think>### UVM中Interface Clocking Block的作用详解 在UVM验证环境中,Interface中的Clocking Block是解决**Testbench与DUT之间时序同步**的核心机制,主要作用如下: #### 1. **精确控制信号时序** [^2][^3] - **输入采样时序**:通过`default input #1step`确保在时钟沿**之前**采样稳定值 ```systemverilog clocking cb @(posedge clk); default input #1step; // 采样时钟沿前的稳态值 input data; // 避免采样到过渡态 endclocking ``` - **输出驱动时序**:通过`default output #2`确保在时钟沿**之后**驱动信号 ```systemverilog default output #2; // 时钟沿后驱动,模拟真实寄存器延迟 output valid; // 满足DUT的建立/保持时间 ``` #### 2. **消除信号竞争(Race Condition)** [^2][^5] - **经典问题**:当Testbench与DUT同时操作信号时可能产生冲突 - **解决方案**: - 输入信号:采样时钟沿**前**的稳态值(`#1step`) - 输出信号:驱动时钟沿**后**的值(如`#2`) - **效果**:建立明确的时序窗口,避免采样到亚稳态或未稳定信号[^5] #### 3. **简化Testbench代码** [^1][^2] - 封装时序控制逻辑,Driver/Monitor只需操作Clocking Block: ```systemverilog // 在Driver中驱动信号 dif.cb.data <= $urandom; // 自动应用#2延迟 dif.cb.valid <= 1; // 在Monitor中采样信号 @(dif.cb); // 自动在时钟沿前采样 rec_data = dif.cb.data; // 获取稳定值 ``` - 消除显式时序控制(如`#延迟`),提高代码可维护性[^2] #### 4. **支持同步化操作** [^3][^4] -UVM组件无缝集成: - Driver通过Clocking Block驱动DUT输入[^4] - Monitor通过Clocking Block采样DUT输出[^5] - 关键操作: ```systemverilog @(clocking_handle); // 等待时钟事件 clocking_handle.signal; // 同步访问信号 ``` #### 5. **多时钟域处理** [^3] - 在单个Interface中定义多个Clocking Block: ```systemverilog clocking cb_core @(posedge core_clk); input #1step core_data; endclocking clocking cb_io @(posedge io_clk); output #2 io_valid; endclocking ``` > **典型应用场景**: > 在APB/UART等协议验证中,Clocking Block确保: > 1. Monitor在时钟沿前采样稳定状态[^5] > 2. Driver在时钟沿后驱动新值[^2] > 3. 自动满足协议时序要求[^3] --- ### 相关问题 1. `default input #1step`和`default input #0`在采样行为上有何本质区别?[^2][^5] 2. 当Interface包含多个时钟域时,如何正确规划Clocking Block结构?[^3] 3. 在UVM Monitor中直接使用`@(posedge clk)`代替`@(clocking_block)`会导致什么时序风险?[^5] 4. Clocking Block中的`default output`延迟值应如何根据DUT特性确定?[^2][^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值