SV学习笔记(二)

接口

什么是接口?

  • 接口 主要用作验证 ,国外有些团队会使用sv进行设计,那么接口就会用作设计。
  • 验证环境中,接口可以 使连接变得简洁而不易出错 。
  • interface和module的使用性质很像, 可以定义端口,也可以定义双向信号,可以使用initial和always,也可以定义function和task 。
  • interface可以 在硬件域和软件域间传递信息 ,也就是可以作为module的端口列表,也可以作为软件方法的形式参数。
  • 对于interface的初步认识,可以看作“插排”,DUT与TB之间的数据驱动就是靠这个“插排”来完成的。

接口的定义与使用

  • interface的定义结构与module类似。

  • interface的 端口列表只需定义时钟、复位等公共信号 ,或者不定义任何端口信号,而在变量列表中定义DUT与TB连接的各个变量, 建议用logic来定义 。

  • interface也可以依靠参数化方式提高复用性。

  • interface在例化时, 与module例化方式相同 。

  • 对于有对应interface的DUT和TB组件,在例化时,传递匹配的interface变量名也就完成了interface内变量的传递,换句话说就是两者打通,对应interface的不同组件之间变量信号实时传递。

白话一刻

在System Verilog中,interface是一个用于封装一组相关信号并简化模块间连接的重要特性。它类似于一条总线或“插排”,可以将零碎的线(即信号)整合在一起,提供给需要的模块使用。

  • DUT (Device Under Test):这是指正在被测试或验证的硬件设备或软件组件。在System Verilog的验证环境中,DUT是被测试的对象,测试人员会为其编写测试用例以检查其是否按预期工作。

  • TB (Testbench):测试平台或测试基准。在System Verilog中,TB是用于验证DUT正确性的代码集合。它通常包括DUT的实例化、接口的实例化、验证环境的实例化以及实际的测试用例。 TB的主要任务是产生激励信号并观察DUT的响应,以验证DUT是否满足预期的行为。

在interface的上下文中,从TB的角度来看,interface中的信号输入输出方向与TB中信号的输入输出方向是一致的。而从DUT的角度来看,DUT中信号的输入输出方向与interface中信号的输入输出方向是相反的。这是因为interface在TB和DUT之间起到了一个中介或桥梁的作用,确保两者之间的数据流动是正确和高效的。

总结来说,interface在System Verilog的验证环境中扮演了关键角色,它简化了DUT和TB之间的连接和数据交换,使得验证工作更加高效和准确。而DUT是被测试的对象,TB则是用于验证DUT的测试代码集合。

interface arb_interface(input bit clk);
    logic [1:0] grant, request;
    logic reset_n;
endinterface

module arb(arb_interface arb_if);
    //......
    always@(posedge arb_if.clk or negedge arb_if.reset_n)begin
        if(!arb_if.reset_n)
            arb_if.grant <= 2'b00;
        else
            arb_if.grant <= next_grant;
    end
    //......
endmodule

module testbench(arb_interface arb_if);
    //......
    initial begin
        @(posedge arb_if.clk) arb_if.request <= 2'b01;
        $display("@%0t: Drove req=01", $time);
        repeat(2) @(posedge arb_if.clk);
        if(arb_if.grant != 2'b01) $display("@%0t: a1: grant != 2'b01", $time);
        $finish;
    end
endmodule

module tb_top;
    bit clk;
    initial clk = 1'b0;
    always #5 clk = ~clk;

    arb_interface arb_if(clk);
    arb a1(arb_if);
    test t1(arb_if);
endmodule

这段代码是使用SystemVerilog语言编写的,主要涉及到接口(interface)和模块(module)的定义。我会为你解释每个部分的功能。

    1. arb_interface 定义
interface arb_interface(input bit clk);  
    logic [1:0] grant, request;  
    logic reset_n;  
endinterface

这里定义了一个名为arb_interface的接口,它有一个输入信号clk。接口内部定义了两个2位的逻辑信号grant和request,以及一个逻辑信号reset_n。

    1. arb 模块
module arb(arb_interface arb_if);  
    //......  
    always@(posedge arb_if.clk or negedge arb_if.reset_n)begin  
        if(!arb_if.reset_n)  
            arb_if.grant <= 2'b00;  
        else  
            arb_if.grant <= next_grant;  
    end  
    //......  
endmodule

arb模块使用arb_interface作为它的接口。它有一个始终块,该块在clk的上升沿或reset_n的下降沿触发。当reset_n为假时,grant信号被设置为2’b00;否则,它被设置为next_grant的值(这里next_grant`在代码段中没有定义,可能是在模块的其他部分定义的)。

    1. testbench 模块
module testbench(arb_interface arb_if);  
    //......  
    initial begin  
        @(posedge arb_if.clk) arb_if.request <= 2'b01;  
        $display("@%0t: Drove req=01", $time);  
        repeat(2) @(posedge arb_if.clk);  
        if(arb_if.grant != 2'b01) $display("@%0t: a1: grant != 2'b01", $time);  
        $finish;  
    end  
endmodule

testbench模块也是一个使用arb_interface的模块。在initial块中,它首先等待clk的上升沿,然后将request信号设置为2’b01。接着,它等待clk的另外两个上升沿,并检查grant信号是否为2’b01`。如果不是,它会打印一条错误消息,并结束仿真。

    1. tb_top 模块
systemverilog
module tb_top;  
    bit clk;  
    initial clk = 1'b0;  
    always #5 clk = ~clk;  
  
    arb_interface arb_if(clk);  
    arb a1(arb_if);  
    test t1(arb_if);  
endmodule

tb_top是顶层模块,它定义了时钟信号clk,并通过always块每5个时间单位反转clk的值。然后,它实例化了arb_interface、arb和testbench模块,并将它们连接在一起。这样,testbench模块可以向arb`模块发送请求,并检查其响应。

总结:这段代码是一个简单的SystemVerilog验证环境,其中包含一个接口、一个仲裁器模块和一个测试基准模块。顶层模块将它们连接在一起,以测试仲裁器的行为。

这里着重解释一下tb_top 模块:

在 tb_top 模块中,test t1(arb_if); 这一行代码是实例化 testbench 模块(在这里被重命名为 test)的一个实例 t1,并将之前定义的 arb_interface 实例 arb_if 作为参数传递给 test 模块的接口。

详细解释如下:

  • 实例化模块:在SystemVerilog中,模块可以被多次实例化,以创建多个独立的模块实例。每个实例可以有其自己的内部状态和行为,但它们都基于相同的模块定义。在这里,test t1(arb_if); 就是创建 testbench 模块的一个新实例 t1。

  • 接口传递:arb_interface arb_if(clk); 这行代码在 tb_top 模块中定义了一个 arb_interface 的实例 arb_if,并将 tb_top 中定义的 clk 信号连接到该接口的 clk 输入。然后,在实例化 test 模块时,arb_if 被作为参数传递给 test 模块,这样 test 模块就可以通过 arb_if 接口与 arb 模块进行通信。

  • 连接性:由于 arb_if 被传递给了 test 和 arb 两个模块,这两个模块可以通过 arb_if 接口来交换信号。在 test 模块中,它可以通过 arb_if 发送请求(request)并观察响应(grant)。同时,arb 模块也可以通过 arb_if 接收请求并发送响应。

  • 封装性:通过使用接口,test 和 arb 模块都不需要知道彼此的内部实现细节。它们只需要知道如何通过 arb_if 接口来交换信号。这种封装性使得模块的设计和测试更加灵活和可维护。

  • 测试环境:在这种上下文中,test 模块通常用作测试环境或测试台(testbench),用于验证 arb 模块的行为是否符合预期。通过发送不同的请求并观察响应,测试人员可以验证 arb 模块的功能正确性。

综上所述,test t1(arb_if); 这行代码在 tb_top 模块中创建了一个 testbench 模块的实例 t1,并通过 arb_interface 实例 arb_if 将它与 arb 模块连接起来,从而构建了一个完整的测试环境。

module testbench(arb_interface arb_if);  
    //......  
    initial begin  
        @(posedge arb_if.clk) arb_if.request <= 2'b01;  
        $display("@%0t: Drove req=01", $time);  
        repeat(2) @(posedge arb_if.clk);  
        if(arb_if.grant != 2'b01) $display("@%0t: a1: grant != 2'b01", $time);  
        $finish;  
    end  
endmodule

接口的优势

  • 将有关信号封装在接口,对于设计和验证环境都便于维护,如需修改、添加、删除信号,只需修改interface文件即可。
  • 接口在硬件域(module)和软件域(class)都可以使用,是 硬件域和软件域交互的唯一媒介
  • 接口可例化,对于多组相同总线,通过例化可灵活使用,简化代码且便于维护。
  • 每一个agent使用对应的interface,简化验证平台结构,便于维护。
  • tb顶层例化时,无需定义信号连线,只需例化interface。

采样和数据驱动

竞争问题

  • 为了避免RTL仿真行为中发生信号竞争问题, 建议使用非阻塞赋值(<=) (简单来说阻塞赋值是顺序执行,非阻塞赋值是并发执行,硬件电路行为是并发执行)。

  • 在仿真行为中,为了避免时序电路中时钟和驱动信号的时序竞争,我们需要 尽量明确驱动时序和采样时序 。

  • 默认情况下,时钟对于组合电路的驱动会添加一个 无限最小时间(delta-cycle)的延迟 ,而该延迟无法用绝对时间单位衡量,它要比最小时间单位精度还要小。这是 仿真工具为了符合硬件电路真实行为(建立时间保持时间以及线延迟)而做出的处理 。(注意: #0并不代表0延迟,而是指延迟delta-cycle)

为了说明delta-cycle的概念,举例如下:

`timescale 1ns/1ps
module top_module ();
        bit clk1,clk2;
    bit rstn;

    logic [7:0] d1;

    initial begin
        clk1 = 0;
        forever #5 clk1 <= ~clk1;
    end

    always@(clk1) clk2<=clk1;

    initial begin
        #0 rstn <= 0;
        #10 rstn <= 1;
        #20 $finish;
    end

    always@(posedge clk1 or negedge rstn)
        if(!rstn) d1 <= 0;
        else d1 <=  d1+1;

    always@(posedge clk1)
        $display("clk1: %0t ns d1 value is 0x%0x", $time, d1);
    always@(posedge clk2)
        $display("clk2: %0t ns d1 value is 0x%0x", $time, d1);
endmodule
Running Icarus Verilog simulator...
VCD info: dumping is suppressed.
clk1: 5000 ps d1 value is 0xxx
clk2: 5000 ps d1 value is 0x0
clk1: 15000 ps d1 value is 0x0
clk2: 15000 ps d1 value is 0x1
clk1: 25000 ps d1 value is 0x1
clk2: 25000 ps d1 value is 0x2
Hint: Total mismatched samples is 0 out of 0 samples

Simulation finished at 30000 ps
Mismatches: 0 in 0 samples

白话一刻:

这是一个简单的Verilog模块,它描述了一个带有复位功能的计数器。下面是这段代码的详细解释:

时序单位

`timescale 1ns/1ps

这一行定义了时间单位和精度。在这里,时间单位是1纳秒(ns),精度是1皮秒(ps)。

模块定义

module top_module ();

定义了一个名为top_module的模块。

信号定义

bit clk1,clk2;  
bit rstn;  
logic [7:0] d1;

clk1 和 clk2 是位(bit)类型的信号,用作时钟。
rstn 是复位信号,低电平有效。
d1 是一个8位宽的逻辑信号,用作计数器。

时钟生成

initial begin  
    clk1 = 0;  
    forever #5 clk1 <= ~clk1;  
end

这个initial块生成了一个时钟信号clk1,它的周期是10个时间单位(即10ns)。

时钟同步

always@(clk1) clk2<=clk1;

这个always块确保clk2与clk1同步。每当clk1变化时,clk2都会获得clk1的当前值。

复位逻辑

initial begin  
    #0 rstn <= 0;  
    #10 rstn <= 1;  
    #20 $finish;  
end

这个initial块设置了复位信号rstn。

在仿真开始时,rstn为0(有效复位)。

在10个时间单位后,rstn变为1(解除复位)。在20个时间单位后,仿真结束。

计数器逻辑

always@(posedge clk1 or negedge rstn)  
    if(!rstn) d1 <= 0;  
    else d1 <=  d1+1;

这个always块描述了一个计数器。每当clk1的上升沿发生或rstn的下降沿发生时,都会执行这个块。

如果rstn为0(有效复位),则d1被清零。否则,d1增加1。

显示逻辑

always@(posedge clk1)  
    $display("clk1: %0t ns d1 value is 0x%0x", $time, d1);  
always@(posedge clk2)  
    $display("clk2: %0t ns d1 value is 0x%0x", $time, d1);

这两个always块在每次clk1和clk2的上升沿时显示当前时间和d1的值。

总结

这个模块生成了两个同步的时钟信号clk1和clk2,以及一个复位信号rstn。它实现了一个简单的计数器,该计数器在复位时清零,并在每个clk1的上升沿时递增。同时,它还使用$display系统任务来显示时钟信号和计数器的值。

为什么同样在25000ps时刻,d1在clk1和clk2下的采样值不同?

首先clk2是在clk1下驱动的,也就是clk2比clk1延迟一个delta-cycle时间,clk1驱动了d1,d1也比clk1延迟一个delta-cycle时间。

25000ps,当在clk1下采样时,d1的值还未更新,所以采到的是0x1;

而在clk2下采样时,d1已由clk1驱动更新,所以采到的是0x2。

如果还未完全理解,可以打开波形窗口,不过要打开delta-cycle的开关,可以看到delta-cycle的存在,对电路的数据驱动和采样时序会有更直观的理解。

,clk2 是通过 always@(clk1) 块中的语句 clk2 <= clk1; 驱动的。这个语句意味着每当 clk1 发生变化时,clk2 将被赋值为 clk1 的当前值。但是,由于 <= 是非阻塞赋值,它不会立即更新 clk2 的值,而是在当前仿真时间槽的末尾更新。

非阻塞赋值的一个关键特性是,在一个给定的仿真时间槽内,所有非阻塞赋值语句都看起来是同时执行的,即它们都在该时间槽的末尾更新其目标。因此,尽管 clk2 的赋值是基于 clk1 的变化,但由于非阻塞赋值的特性,clk2 的更新实际上会延迟一个delta-cycle。

  • 总结:

    • 如果处于各种原因,clk与被采样数据之间存在若干个delta-cycle的延迟,那么对数据的采样会存在问题。
    • 采样数据的竞争问题会成为潜在困扰仿真采样准确性的问题。
    • 避免采样的竞争问题:
      • 1)在驱动时,添加相应的人为延迟,使clk与驱动变量之间的延迟加大,提高DUT使用驱动信号时的准确度;
      • 2)在采样时,依靠采样前某段时刻进行采样,来模拟建立时间的采样要求,确保采样的可靠性。

图示:

未显示 delta-cycle

显示 delta-cycle

在仿真过程中,delta-cycle的延迟存在主要是由于以下几个原因:
首先,delta-cycle是一个无限小的时间单位,其精度比最小的时间单位还要小,无法用绝对时间单位来描述。在仿真中,当运行一个delta-cycle时,实际上是让仿真器在一个极短的时间内执行一系列的操作。这种极短的延迟有助于理解在“同一时刻”的采样或驱动的先后逻辑关系。
其次,在RTL(寄存器传输级)仿真中,一些信号变化可能在少于单位时间(如纳秒级)内发生,这使得信号的变化过程变得不确定。通过引入delta-cycle作为额外的时刻,可以更清晰地描述这些细微变化,使仿真过程更加逻辑化。
此外,delta-cycle的延迟还用于解决同一时间点的信号竞争问题。在组合逻辑电路中,由于信号竞争状态的存在,可能会导致冒险的情况产生。为了避免这种情况,通常在组合逻辑电路的驱动输出中添加一个delta-cycle的延迟。这种延迟可以确保信号在发生变化时,按照预期的先后顺序进行处理,从而避免信号竞争带来的问题。
需要注意的是,由于delta-cycle的延迟非常小,它通常不会对整体仿真结果产生显著影响。然而,在某些特定情况下,如采样数据中的竞争问题,这种微小的延迟可能会导致采样结果的不一致。因此,在进行仿真时,需要仔细考虑delta-cycle的延迟对仿真结果的影响,并采取相应的措施来避免潜在的问题。

仿真时间槽的具体长度在Verilog中并没有一个固定的值,因为它取决于当前仿真时刻发生的事件数量以及仿真器的实现。

clocking

  • 在接口中声明clocking(时序块)和采样的时钟信号,可以用来 实现信号的同步和采样 。
  • clocking块基于时钟周期对信号进行驱动或采样的方式,使testbench不再苦恼于如何准确及时地对信号驱动或采样,消除了信号竞争的问题。
clocking bus @(posedge clk1);
    default input #10ns output #2ns;
    input data, ready, enable;
    output negedge ack;
    input #1step addr;
endclocking

这行定义了一个输出信号ack,并且指定了它的有效边沿为下降沿(negedge)。这意味着ack信号的变化(从高电平到低电平)将在clk1的上升沿之后的某个时刻被输出,具体取决于可能存在的其他延迟设置或操作。

其中定义了一个名为addr的输入信号,并给它指定了一个特殊的延迟#1step。#1step通常用于描述跨时钟域的传输,表示该信号的值将在下一个时钟边沿(在本例中是clk1的下一个上升沿)时被采样。这通常用于避免亚稳态问题,确保信号在稳定后才被采样。


对上述clocking描述代码进行说明 :

  • 第一行定义clocking块bus,使用上升沿来驱动和采样。
  • 第二行指出输入信号在clk1上升沿之前5ns采样,输出信号在clk1上升沿之后2ns驱动(输入为采样,输出为驱动)。
  • 第三行声明输入信号,采用默认的输入事件(clk1上升沿5ns前采样)。
  • 第四行声明输出信号,并且指明为clk1下降沿驱动,覆盖了原有的clk1上升沿后2ns驱动。
  • 第五行定义了输入信号addr,采用了自定义的采样事件,clk1上升沿后的1 step,覆盖了原有的clk1上升沿前5ns采样,这里1 step使得采样发生在clk1上升沿的上一个时钟片采样区域,即可以保证采样到的数据是上一个时钟周期数据。

clocking块的总结 :

  • clocking块不仅可以定义在interface中,也可以定义在module和program中。
  • clocking中列举的信号不是自己定义的,而是interface或其他声明clocking的模块定义的。
  • clocking在声明完后,应该伴随着定义默认的采样事件,也就是“default input/output event”,如果没有定义,会默认使用时钟上升/下降沿前1step进行采样,时钟上升/下降沿后#0进行驱动
  • 除了定义默认的采样和驱动事件,定义信号方向时同样可以用新的采样/驱动事件对默认事件进行覆盖。

在Verilog中,clocking块用于描述在特定时钟边沿下输入和输出信号的行为。当声明一个clocking块时,确实应该伴随着定义默认的输入和输出事件,即指定输入信号的采样时刻和输出信号的驱动时刻。

如果没有在clocking块中显式定义默认的输入和输出事件,Verilog会采用一些默认的规则来确定这些事件。具体来说:

  • 默认输入事件:如果没有明确指定输入信号的采样事件,**则默认会在时钟边沿前的1个时间步长(#1step)进行采样。**这样做是为了确保在时钟边沿到来之前,输入信号的值已经稳定,从而避免可能的亚稳态问题。

  • 默认输出事件:对于输出信号,如果没有明确指定驱动事件,则默认会在时钟边沿后立即(#0)进行驱动。这意味着输出信号的新值会紧跟在时钟边沿之后被更新。

这种默认行为确保了即使在没有明确指定的情况下,clocking块内的信号也有一个清晰且合理的行为模型。然而,在实际应用中,为了增加代码的可读性和准确性,通常建议显式地定义输入和输出事件的采样和驱动时刻,而不是依赖于这些默认行为。

通过显式定义输入和输出事件,您可以更精确地控制信号的行为,确保它们符合硬件设计的预期。例如,您可以指定输入信号在时钟上升沿后的某个延迟后进行采样,或者指定输出信号在时钟下降沿前的某个时刻进行驱动。这些自定义的设置可以帮助您更准确地模拟硬件的行为,并有助于发现和解决潜在的问题。

modport

modport本身目的就是将信号分组和指明输入输出方向,在连接时工具会进行端口方向检查,防止连接出错,不过掌握clocking之后,可以忽略modport,clocking已经对信号输入输出方向进行了声明。

结论

  • 为了避免采样竞争问题,验证工程师应该在验证环境的驱动环节添加固定延迟,使得在仿真波形中更容易体现出时钟与被驱动信号之间的时序前后关系,同时这样也便于对DUT的准确处理和TB的准确采样。
  • 如果TB在采样从DUT送出的数据,在时钟与被驱动信号之间存在delta-cycle时,应该考虑在时钟采样沿的更早时间端段去模拟建立时间要求,这种方法也可以避免由于delta-cycle问题带来的采样竞争问题。
  • 当我们把clocking运用到interface中,用来声明各个接口与时钟的采样和驱动关系后,可以大大提高数据驱动和采样的准确性,从根本上消除采样竞争的可能性。

测试的开始和结束

写在前头

  • 各个设计自身可以认为是一个大的线程,内部有包含多个并行的线程,而模块之间连接即线程的通信,主要依靠信号的变化。
  • 可以想象,对于一个设计,如果在仿真开始没有任何激励,那么仿真不具备执行条件,也可以认为已经结束,因为在设计内部没有产生任何新的事件,也不会触发组合逻辑和时序逻辑。
  • 如果仿真开始后仅提供时钟和复位信号,验证会持续下去,而对设计不会产生实质的功能影响。从设计角度来看,复位信号是为了让设计进入一个确定的初始状态,而时钟就是脉搏跳动
  • verilog测试中,可以通过系统函数 “ f i n i s h ( ) ”来结束仿真,也可以通过“ finish()” 来结束仿真,也可以通过 “ finish()来结束仿真,也可以通过stop()” 来暂停仿真。

program

  • program是作为验证而提出的,可以有效控制仿真的进程,但是目前验证平台更多基于UVM,UVM有独特的控制机制,所以program在实际项目中使用并不多。
  • program的提出, 将验证部分和设计部分进行有效隔离 ,每一个program作为一个独立测试, 当testbench中所有program中最后一个initial块完成后,结束仿真 。这是program的隐式结束。
  • 有些program内的initial块无法正常结束,这时候需要使用 显示结束,使用“$exit()” 来结束program。
  • program被看做软件域,所以不可以出现always、module、interface等硬件相关语句,并且不可以例化其他program。
  • program被看做软件域,可以在program内部定义变量和发起多个initial块,并且建议使用阻塞赋值(软件方式的顺序执行)。
  • program对于数据采样也可以消除delta-cycle竞争问题,详细内容可见红宝书“SV环境构建篇之程序和模块”。(待了解)

总结

  • 硬件域(module)、软件域(program)、中间域(interface) 。

  • 不仅可以使用interface clocking来消除采样竞争问题,可以使用program(建议使用clocking)。
    program可以控制仿真的结束。

  • 使用“ s t o p ( ) ”和“ stop()”和“ stop()finish()”可以结束仿真。

调试方法

调试工具

大多工程师选择使用verdi作为调试工具,主要有三个窗口:层级列表窗口、源代码窗口、波形窗口。verdi的具体使用方法,请参考另一篇帖子【Verdi使用总结】 。

打印消息

打印消息是调试循环语句、顺序执行语句等查看路径和当前变量值的简便方式,除此之外,由于验证平台更多变量是动态的,无法在调试工具查看动态变量值,所以对与验证环境的调试,更多使用打印消息。

打印消息命令“$display()”:

  • $time代表仿真时间变量。
  • 显示格式: %x(十六进制)、%d(十进制)、%b(二进制)、%s(字符串)、%t(时间)。
  • d i s p l a y (消息级别)、 display(消息级别)、 display(消息级别)、warning(警告级别)、 e r r o r (错误级别)、 error(错误级别)、 error(错误级别)、fatal(严重错误级别)
  • 字符串变量格式化:string s = $sformatf(“Hello, %s!", name_s);

设置断点

  • 可以通过调试工具为程序设置断点。
  • 通过设置断点(breakpoint)可以查看程序执行到断点处(程序暂停)的变量数值,而设置断点要求验证工程师对程序执行顺序足够了解。
  • 设置断点可以便于查看软件程序(function、task、object)中局部变量的数值。注意:动态变量是无法添加到波形查看的。
  • 设置断点还可以方便调试程序执行的顺序,例如在顺序执行语句执行的多个位置设置断点,通过仿真执行,查看程序是否在断点处暂停,如果没有,那么程序的挂起(hang-on)原因就在上一个断点和此断点之间。通过此方法可定位可疑程序的范围。
  • 如果查看局部变量,需要使用局部变量窗口(Local Windows),继而通过断点查看变量(暂时不知verdi是否支持,待学习)。

白话一刻:

在Verilog硬件描述语言(HDL)中,理解阻塞赋值(blocking assignment)和非阻塞赋值(nonblocking assignment)是非常重要的,特别是在描述并发行为时。这两种赋值方式会影响仿真中信号值的更新顺序,并可能导致所谓的“竞争条件”。

  • 竞争条件
    竞争条件(Race Condition)指的是在仿真过程中,当多个语句或操作试图在同一仿真时间槽(time-slot)内修改同一个信号或变量时,如果这些操作的执行顺序不同,可能会导致不同的执行结果。换句话说,竞争条件是由于多个操作之间的时间依赖关系不明确导致的。

在硬件设计中,并发性是一个关键概念,因为许多事件(如信号变化)可能几乎同时发生。如果Verilog代码中的赋值操作不能正确反映这种并发性,那么仿真结果可能与实际硬件行为不一致。

  • 阻塞赋值
    阻塞赋值使用等号(=)作为操作符。当执行阻塞赋值时,赋值语句会立即计算右侧表达式(RHS)的值,并将其赋给左侧表达式(LHS)。在这个赋值完成之前,仿真器会暂停执行任何其他语句。换句话说,阻塞赋值是顺序执行的

这种赋值方式在描述组合逻辑时很有用,因为组合逻辑的输出直接依赖于其当前输入。但是,在描述时序逻辑时,如果不恰当地使用阻塞赋值,可能会导致竞争条件。

  • 非阻塞赋值
    非阻塞赋值使用小于等于号(<=)作为操作符。与阻塞赋值不同,非阻塞赋值不会立即更新LHS的值。相反,它会在当前仿真时间槽结束时更新LHS的值。这意味着在当前时间槽内,即使已经计算了RHS的值,其他语句仍然可以继续执行,并可能修改LHS的值。

非阻塞赋值允许模拟时序逻辑中的并发行为,因为所有非阻塞赋值语句在同一时间槽内看起来是同时执行的。这使得非阻塞赋值在描述寄存器传输级(RTL)时序逻辑时非常有用。

  • 使用建议
    为了避免在RTL仿真行为中发生信号竞争问题,建议在描述时序逻辑时使用非阻塞赋值。这是因为时序逻辑通常涉及状态寄存器和时钟信号,这些信号的行为在硬件中是并发的。使用非阻塞赋值可以更好地模拟这种并发行为,并减少竞争条件的风险。

相反,在描述组合逻辑时,由于输出直接依赖于输入,使用阻塞赋值通常是合适的。但是,即使在组合逻辑中,也要小心避免潜在的竞争条件,特别是当存在多个路径可以修改同一信号时。

总的来说,正确选择使用阻塞赋值还是非阻塞赋值是确保Verilog代码正确模拟硬件行为的关键。


参考资料

  • Wenhui’s Rotten Pen
  • SystemVerilog
  • chipverify

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/519220.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

文心一言 VS 讯飞星火 VS chatgpt (230)-- 算法导论17.1 1题

一、如果栈操作包括 MULTIPUSH 操作&#xff0c;它将 k 个数据项压入栈中&#xff0c;那么栈操作的摊还代价的界还是 O(1) 吗? 文心一言&#xff1a; 在栈的基本操作中&#xff0c;我们通常考虑的是单个元素的压入&#xff08;push&#xff09;和弹出&#xff08;pop&#x…

升级程序到Java21的记录二(修改程序源代码)

背景 :参看 《升级程序到Java21的记录一&#xff08;先升级jdk到21&#xff09;》&#xff0c; Jdk升级完毕&#xff0c;下面我们该秀修改程序源代码了&#xff0c; 程序最初使用的springboot2.6.8 以及jdk17。为了使用springboot 3.0&#xff08;3.0开始有支持虚拟线程的相关…

抖音运营技巧

1、视频时长 抖音的作品是否能够继续被推荐&#xff0c;取决于综合数据&#xff0c;包括完播率、点赞率、评论率、转发率和收藏率等。其中&#xff0c;完播率是最容易控制的因素。对于新号来说&#xff0c;在没有粉丝的初期&#xff0c;发布过长的视频可能会导致无人观看。因此…

Day31|贪心算法part01:理论基础、455.分发饼干、376. 摆动序列、53. 最大子序和

理论基础 记得贪心没有规律即可&#xff01;解不出来就看题解。 455. 分发饼干 先把学生和饼干都排序&#xff08;Arrays.sort只能升序&#xff09;&#xff0c;然后都从后往前遍历&#xff0c;把最大的饼干给需求最大的孩子&#xff08;贪心&#xff09; class Solution {…

4核8G服务器配置性能怎么样?4核8G12M配置服务器能干啥?

腾讯云4核8G服务器多少钱&#xff1f;腾讯云4核8G轻量应用服务器12M带宽租用价格646元15个月&#xff0c;活动页面 txybk.com/go/txy 活动链接打开如下图所示&#xff1a; 腾讯云4核8G服务器优惠价格 这台4核8G服务器是轻量应用服务器&#xff0c;详细配置为&#xff1a;轻量4核…

内网安全之-kerberos协议

kerberos协议是由麻省理工学院提出的一种网络身份验证协议&#xff0c;提供了一种在开放的非安全网络中认证识别用户身份信息的方法。它旨在通过使用秘钥加密技术为客户端/服务端应用提供强身份验证&#xff0c;使用kerberos这个名字是因为需要三方的共同参与才能完成一次认证流…

【C++】stack和queue

个人主页 &#xff1a; zxctscl 如有转载请先通知 文章目录 1. stack的介绍和使用1.1 stack的介绍1.2 stack的使用1.3 stack的模拟实现 2. queue的介绍和使用2.1 queue的介绍2.2 queue的使用2.3 queue的模拟实现 3. 容器适配器3.1 概念3.2 STL标准库中stack和queue的底层结构3.…

@RequestParam和@PathVariable的区别

同样都是接收URL中的参数&#xff0c;RequestParam和PathVariable有什么区别呢&#xff1f;

随手集☞Spring知识盘点

概述 定义 Spring框架的提出者是程序员Rod Johnson&#xff0c;他在2002年最早提出了这个框架的概念&#xff0c;随后创建了这个框架。Spring框架的目标是简化企业级Java应用程序的开发&#xff0c;通过提供一套全面的工具和功能&#xff0c;使开发者能够更加高效地构建高质量…

Prometheus+grafana环境搭建MongoDB(docker+二进制两种方式安装)(五)

由于所有组件写一篇幅过长&#xff0c;所以每个组件分一篇方便查看&#xff0c;前四篇mongodb的exporter坑也挺多总结一下各种安装方式&#xff0c;方便后续考古。 Prometheusgrafana环境搭建方法及流程两种方式(docker和源码包)(一)-CSDN博客 Prometheusgrafana环境搭建rabb…

5分钟润色一篇论文:ChatGPT意味着什么?Nature连发两篇文章探讨

2023年随着OpenAI开发者大会的召开&#xff0c;最重磅更新当属GPTs&#xff0c;多模态API&#xff0c;未来自定义专属的GPT。微软创始人比尔盖茨称ChatGPT的出现有着重大历史意义&#xff0c;不亚于互联网和个人电脑的问世。360创始人周鸿祎认为未来各行各业如果不能搭上这班车…

服务器硬件构成与性能要点:CPU、内存、硬盘、RAID、网络接口卡等关键组件的基础知识总结

文章目录 服务器硬件基础知识CPU&#xff08;中央处理器&#xff09;内存&#xff08;RAM&#xff09;硬盘RAID&#xff08;磁盘阵列&#xff09;网络接口卡&#xff08;NIC&#xff09;电源散热器主板显卡光驱 服务器硬件基础知识 服务器是一种高性能计算机&#xff0c;用于在…

第1章:芯片及引脚介绍

芯片及引脚介绍 1&#xff1a; 芯片介绍1.1&#xff1a;芯片系列1.2 &#xff1a;STM32F103C8T6型号的介绍 2&#xff1a;引脚2.1&#xff1a;寄存器2.2&#xff1a;最小系统板 3&#xff1a;最小系统板的引脚3.1&#xff1a;特殊引脚3.2&#xff1a;普通引脚3.3&#xff1a;最…

Linux之信号

1.常见信号 虽然最开始的编号是1&#xff0c;最后的编号是64&#xff0c;但是并不是有64个信号&#xff0c;没有32和33号信号&#xff0c;也就是说&#xff0c;一共有62个信号&#xff0c;前31个信号是标准信号&#xff08;非实时信号&#xff09;&#xff0c;后31个信号是实时…

Android自定义view;实现掌阅打开书籍动画效果

这里利用自定义view的方式来处理&#xff0c;初始化数据&#xff0c;camera通过setLocation调整相机的位置&#xff0c;但是Camera 的位置单位是英寸&#xff0c;英寸和像素的换算单位在 Skia 中被写成了72 像素&#xff0c;8 x 72 576&#xff0c;所以它的默认位置是 (0, 0, …

Linux基础篇:Linux网络yum源——以配置阿里云yum源为例

Linux网络yum源——以阿里云为例 一、网络yum源介绍 Linux中的YUM&#xff08;Yellowdog Updater, Modified&#xff09;源是一个软件包管理器&#xff0c;它可以自动处理依赖关系并安装、更新、卸载软件包。YUM源是一个包含软件包的远程仓库&#xff0c;它可以让用户轻松地安…

用户账号和组账号及管理

用户账号和组账号 Linux中每个用户是通过 User Id &#xff08;UID&#xff09;来唯一标识的 新建用户 1-60000 自动分配 0-65535 端口号&#xff0c;系统是靠uid来区分用户身份的&#xff0c;用户的uid 为0 就是超级管理员 1.用户账号的类型 超级管理员:权限最高的用户,roo…

Flutter Web 的未来,Wasm Native 即将到来

早在去年 Google I/O 发布 Flutter 3.10 的时候就提到过&#xff0c; Flutter Web 的未来会是 Wasm Native &#xff0c;当时 Flutter 团队就表示&#xff0c;Flutter Web 的定位不是设计为通用 Web 的框架&#xff0c;类似的 Web 框架现在有很多&#xff0c;而 Flutter 的定位…

[lesson06]内联函数分析

内联函数分析 常量与宏回顾 C中的const常量可以替代宏常数定义&#xff0c;如&#xff1a; C中是否有解决方案替代宏代码片段&#xff1f; 内联函数 C中推荐使用内联函数替代宏代码片段 C中使用inline关键字声明内联函数 内联函数声明时inline关键字必须和函数定义结合在…

营销中的归因人工智能

Attribution AI in marketing 归因人工智能作为智能服务的一部分&#xff0c;是一种多渠道算法归因服务&#xff0c;根据特定结果计算客户互动的影响和增量影响。有了归因人工智能&#xff0c;营销人员可以通过了解每个客户互动对客户旅程每个阶段的影响来衡量和优化营销和广告…