SystemVerilog(6):线程通信

1、线程

  • 线程即独立运行的程序。
  • 线程需要被触发,可以结束或者不结束。
  • 在 module 中的 initial 和 always,都可以看做独立的线程,它们会在仿真 0 时刻开始,而选择结束或者不结束。
  • 硬件模型中由于都是 always 语句块,所以可以看成是多个独立运行的线程,而这些线程会一直占用仿真资源,因为它们并不会结束。
  • 软件测试平台中的验证环境都需要由 initial 语句块去创建,而在仿真过程中,验证环境中的对象可以创建和销毁,因此软件测试端的资源占用是动态的。
  • 软件环境中的 initial 块对语句有两种分组方式,使用 begin…endfork…join
  • begin…end 中的语句以 顺序方式执行,而 fork…join 中的语句则以 并发方式执行。
  • 与 fork…join 类似的并行方式语句还包括 fork…join_anyfork…join_none

一些概念的澄清:

  • 线程的执行轨迹是呈树状结构的,即任何的线程都应该有父线程。
  • 父线程可以开辟若干个子线程,父线程可以暂停或者终止子线程。
  • 当子线程终止时,父线程可以继续执行。
  • 当父线程终止时,其所开辟的所有子线程都应当会终止。

2、线程的控制

2.1 三种fork

  • fork_join:最长时间的程序走完,才会结束 fork 块;
  • fork_join_any:最短时间的程序走完,就会结束 fork 块,但 fork 语句里未走完的程序依旧会执行(前提是Initial未结束);
  • fork_join_none:一运行就会结束 fork 块,但 fork 语句里未走完的程序依旧会执行(前提是initial未结束);

SystemVerilog(6):线程通信

2.2 等待所有fork

在sv中,当程序中的 initial 块全部执行完毕,仿真器就退出了。如果我们希望等待 fork 块中的所有线程执行完毕再退出结束 initial 块,我们可以使用 wait fork语句来等待所有子线程结束。

task run_threads ;
    fork
        check_trans (tr1); //线程1
        check_trans (tr2); //线程2
        check_trans (tr3); //线程3
    join_none
    ...

    //等待所有fork中的线程结束再退出task
    wait fork;
endtask

2.2 停止单个fork

在使用了 fork …join any 或者 fork…join_none 以后,我们可以使用 disable来指定需要停止的线程。

parameter TIME_OUT = 1000;

task check_trans( Transaction tr) ;
    fork
        begin
            //等待回应,或者达到某个最大时延
            fork: timeout block
                begin
                    wait(bus.cb.addr == tr.addr) ;
                    $display("@%0t: Addr match %d",$time, tr.addr);
                end
                #TIME_OUT $display("@%0t: Error: timeout",$time);
            join_any
            disable timeout_block ;
        end
    join_none
endtask

disable fork可以停止从当前线程中衍生出来的所有子线程。

initial begin
    check_trans (tro) ; //线程0
    //创建一个线程来限制disable fork的作用范围
    fork //线程1
        begin
            check_trans(tr1); //线程2
            fork //线程3
                check_trans(tr2); //线程4
            join
            //停止线程1-4,单独保留线程0
            #(TIME_OUT/2) disable fork;
        end
    join
end

2.3 停止被多次调用的任务

如果你给某一个任务或者线程指明标号,那么当这个线程被调用多次以后,如果通过disable去禁止这个线程标号,所有衍生的同名线程都将被禁止。

task wait_for_time__out(int id) ;
    if (id == 0)
    fork
        begin
            #2;
            $display ("@%0t: disable wait_for_time_out", $time);
            disable wait_for_time_out;
        end
    join_none
    fork: just_a_little
        begin
            $display ("@0t: 8m: 80d entering thread", $time, id);
            #TIME_OUT;
            $display("@%0t:%m: %0d done", $time, id);
        end
    join_none
endtask

initial begin
    wait_for_time_out(0); //Spawn thread 0
    wait_for_time_out(1); //Spawn thread 1
    wait_for_time_out(2); //Spawn thread 2
    #(TIME_OUT*2)
    $display("@%0t: All done", $time);
end
  • 任务 wait_for_time_out 被调用了三次,从而衍生了三个线程。
  • 线程0在#2延时之后禁止了该任务,而由于三个线程均是”同名”线程,因此这些线程都被禁止了,最终也都没有完成。

3、线程间的通信

  • 测试平台中的所有线程都需要同步并交换数据。一个线程需要等待另一个。
  • 多个线程可能同时访问同一个资源。线程之间可能需要交换数据。
  • 所有这些数据交换和同步称之为线程间的通信(IPC,Interprocess Communication) 。

3.1 事件event

  • Verilog中,一个线程总是要等待一个带@操作符的事件。这个操作符是边沿敏感的,所以它总是阻塞着、等待事件的变化。
  • 其它线程可以通过 ->操作符来触发事件,结束对第一个线程的阻塞。
  • 这就像在打电话时,一个人等待另一个人的呼叫。
  • 唯一不需要 new( ) 的方法。

3.1.1 事件的边沿阻塞

event e1,e2;

initial begin
    $display("@%0t: 1: before trigger", $time);
    -> e1;
    @e2;
    $display("e%0t: 1: after trigger", $time);
end

initial begin
    $display("@%0t: 2: before trigger", $time);
    -> e2;
    @e1;
    $display("@%0t: 2: after trigger",$time);
end

打印结果如下所示:

@0: 1: before trigger
@0: 2: before trigger
@0: 1: after trigger
  • 第一个初始化块启动,触发e1事件,然后阻塞在e2上。
  • 第二个初始化块启动,触发e2事件,然后阻塞在e1上。
  • e1 和 e2 在同一个时刻被触发,但由于 delta cycle 的时间差使得两个初始化块可能无法等到 e1 或者 e2。
  • 所以,更安全的方式可以使用event的方法 triggered ( )

3.1.2 等待事件的触发

  • 可以使用电平敏感的 wait (e1.triggered ( ) ) 来替代边沿敏感的阻塞语句 @e1
  • 如果事件在当前时刻已经被触发,则不会引起阻塞。否则,会一直等到事件被触发为止。
  • 这个方法比起 @而言,更有能力保证,只要 event 被触发过,就可以防止引起阻塞。
event e1,e2;
initial begin
    $display("@%0t: 1: before trigger",$time);
    -> e1;
    wait( e2.triggered() );
    $display("@%0t: 1: after trigger" ,$time);
end

initial begin
    $display("@%0t: 2: before trigger", $time);
    -> e2;
    wait( e1.triggered() ) ;
    $display("@%0t: 2: after trigger", $time);
end

打印结果如下所示:

@0: 1: before trigger
@0: 2: before trigger
@0: 1: after trigger
@0: 2: after trigger

3.2 旗语semaphore

  • semaphore可以实现对同一资源的访问控制。
  • 对于初学者而言,无论线程之间在共享什么资源,都应该使用 semaphore 等资源访问控制的手段,以此避免可能出现的问题。
  • semaphore 有三种基本操作。
  • new( )方法可以创建一个带单个或者多个钥匙的semaphore;
  • 使用 get( )可以获取一个或者多个钥匙;
  • put( )可以返回一个或者多个钥匙。
  • 如果你试图获取一个 semaphore 而希望不被阻塞,可以使用 try get( )函数。
  • 返回 1 表示有足够多的钥匙;
  • 返回 0 则表示钥匙不够。
program automatic test(bus_ifc. TB bus) ;
    sermaphore semn; //创建一-个semaphore

    initial begin
        sen = new(1); //分配一个钥匙
        fork
            sequencer(); //产生两个总线事务线程
            sequencer();
        join
    end

    task sequencer;
        repeat($urandom%10) //随机等待0-9个周期
        @bus.cb;
        sendTrans();        //执行总线事务
    endtask

    task sendTrans;
        sem.get(1); //获取总线钥匙
        @bus.cb;    //把信号驱动到总线上
        bus.cb.addr <= t.addr; ... sem.put(1); 处理完成时把钥匙返回 endtask endprogram< code></=>

3.3 信箱mailbox

  • 线程之间如果传递信息,可以使用 mailbox
  • mailbox 和队列 queue 有相近之处。
  • mailbox 是一种对象,因此也需要使用 new( )来例化。
  • 例化时有一个可选的参数 size 来限定其存储的最大数量。
  • 如果 size 是 0 或者没有指定,则信箱是无限大的,可以容纳任意多的条目。
  • 使用 put( )可以把数据放入 mailbox,使用 get( )可以从信箱移除数据。
  • 如果信箱为满,则 put( ) 会阻塞;
  • 如果信箱为空,则 get( )会阻塞。
  • 使用 peek( )可以获取对信箱里数据的拷贝而不移除它。
  • 线程之间的同步方法需要注意,哪些是阻塞方法,哪些是非阻塞方法,即哪些是立即返回的,而哪些可能需要等待时间的。
program automatic bounded;
    mailbox mbx ;

    initial begin
        mbx = new(1); //&#x5BB9;&#x5668;&#x4E3A;1
        fork
            //Producer&#x7EBF;&#x7A0B;
            for(int i=1; i<4; i++) begin $display("producer: before put (80d)", i); mbx.put(i) ; $display("producer : after end consumer线程 repeat(4) int j; #1ns mbx.get(j); $display("consumer get (80d)",j); join endprogram< code></4;>

打印结果如下所示:

Producer : before put(1)
Producer : after put(1)
Producer : before put(2)
Consumer : after get(1)
Producer : after put(2)
Producer : before put(3)
Consumer : after get(2)
Producer : after put(3)
Consumer : after get(3)

关于mailbox的其它特性也需要加以了解:

  • mailbox 在例化时,通过 new(N)的方式可以使其变为定长(fixed length) 容器。这样在负载到长度N以后,无法再对其写入。如果用 new( ) 的方式,则表示信箱容量不限大小。
  • 除了 put( ) / get( ) / peek( )这样的阻塞方法,用户也可以考虑使用 try_put( ) / try_get( ) / try_peek( )等非阻塞方法。
  • 如果要显式地限定 mailbox 中元素的类型,可以通过 mailbox #(type = T)的方式来声明。

3.4 三种方法的比较

  • event:最小信息量的触发,即单一的通知功能。可以用来做事件的触发,也可以多个 event 组合起来用来做线程之间的同步。
  • semaphore:共享资源的安全卫士。如果多线程间要对某一公共资源做访问,即可以使用这个要素。
  • mailbox:精小的 SV 原生 FIFO。在线程之间做数据通信或者内部数据缓存时可以考虑使用此元素。

参考资料:

[1] 路科验证V2教程

[2] 绿皮书:《SystemVerilog验证 测试平台编写指南》第2版

Original: https://www.cnblogs.com/xianyufpga/p/16486955.html
Author: 咸鱼FPGA
Title: SystemVerilog(6):线程通信

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/541660/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Random在高并发下的缺陷以及JUC对其的优化

    Random可以说是每个开发都知道,而且都用的很6的类,如果你说,你没有用过Random,也不知道Random是什么鬼,那么你也不会来到这个技术类型的社区,也看不到我的博客了。但并…

    Java 2023年6月5日
    097
  • MongoDB排序时内存大小限制和创建索引的注意事项!

    线上服务的MongoDB中有一个很大的表,我查询时使用了 sort()根据某个字段进行排序,结果报了下面这个错误: [Error] Executor error during fi…

    Java 2023年6月5日
    079
  • 【Java学习】API接口数据规范

    在日常开发中,一个优雅的API,必须提供简单明了的响应值,然后根据状态码就可以大概知道问题的所在。这里主要整理一下HTTP状态码和自定义状态码。 1、HTTP状态码 当浏览者访问一…

    Java 2023年6月5日
    0111
  • 图解Dijkstra算法+代码实现

    简介 Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstr…

    Java 2023年6月9日
    0114
  • 识别一个文件的真实格式

    识别一个文件的真实格式 import java.io.File; import java.io.FileInputStream; import java.io.FileNotFou…

    Java 2023年6月7日
    083
  • Android 布局及常用属性

    一、常用属性 控件宽度:layout_width wrap_content match_parent 控件高度:layout_height wrap_content match_p…

    Java 2023年6月5日
    084
  • 源码中的设计模式–模板方法模式(钩子方法)

    在上次《源码中的设计模式–模板方法模式》中分享了有关模板方法设计模式方面的东西,不知道还有印象没,重温下其释义, 模板方法模式在一个方法中定义一个算法的骨架,而将一些步…

    Java 2023年6月9日
    094
  • 内部类

    内部类 一种定义在类中的类,他们是嵌套关系。在 编译成功时,会生成多个 .class文件,分别是 &#x5916;&#x90E8;&#x7C7B;.clas…

    Java 2023年6月5日
    074
  • 删除链表的a/b处结点

    给定链表的头节点head,整数a和b,实现删除位于a/b处节点的函数 例如: 链表:1-2-3-4-5,假设a/b的值为r 如果r等于0,不删除任何结点 如果r位于(0~1/5),…

    Java 2023年6月7日
    077
  • 第二周

    学了个新知识:幂等性👍 在使用pageInfo时警告: Unchecked call to ‘PageInfo(List)’ as a member of …

    Java 2023年6月7日
    0106
  • springboot 定制启动图案

    Spring Boot在启动的时候会显示一个默认的Spring的图案,对应的类为SpringBootBanner 图案输出有以下几种模式,默认是CONSOLE的,即只打印到控制台,…

    Java 2023年5月30日
    0262
  • 高端程序员上班摸鱼指南

    原创:微信公众号 &#x7801;&#x519C;&#x53C2;&#x4E0A;,欢迎分享,转载请保留出处。 哈喽大家好啊,我是Hydra。虽然说…

    Java 2023年6月5日
    0204
  • 设计模式—单例模式

    类型:创建型。 目的:杜绝相同对象的反复创建,提升系统性能。 话不多说,直接看实现方案例。 实现案例 项目启动时加载 public class Test { private sta…

    Java 2023年6月7日
    066
  • android多文件上传,java服务端接收

    Android多文件上传,java服务端接收 1、Android端 代码: String uploadUrl = "http://xxx/uploadFiles&quot…

    Java 2023年6月5日
    080
  • 设计模式——结构性设计模式

    结构性设计模式 针对类与对象的组织结构。(白话:类与对象之间的交互的多种模式 类/对象适配器模式 当需要传入一个A类型参数,但只有B类型类时,就需要一个A类型的适配器装入B类的数据…

    Java 2023年6月14日
    077
  • Mybatis系列全解(三):Mybatis简单CRUD使用介绍

    Mybatis系列全解(三):Mybatis简单CRUD使用介绍 Mybatis系列全解(三):Mybatis简单CRUD使用介绍 – + 前言 Mybaits系列全解…

    Java 2023年6月7日
    081
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球