• SystemVerilog_线程


    SV线程

    语句块

    用来将多个语句组织在一起,使得他们在语法上如同一个语句。

    • 顺序块:语句置于关键字begin和end之间,块中的语句以顺序方式执行。
    • 并行块:关键字fork和join/join_any/join_none之间的是并行块语句,块中的语句并行执行。

    命名块

    给每个块定义的标识名称,将块名加在begin或fork后面;

    • 可以定义块内局部变量
    • 允许定义的块被其他语句调用,如disable语句;

    程序和模块

    • 对于硬件的过程块,他们之间的通信可以理解为不同逻辑、时序块之间的通信或者同步,是通过信号的变化来完成
    • 从硬件实现角度看,verilog 通过always,initial过程语句块和信号数据连接实现进程间通信
    • 可以将不同的module作为独立的程序块,他们之间的同步通过信号的变化(event触发)、等待待定事件(时钟周期)或者时间延时来完成
    • 从软件思维理解硬件仿真,仿真中的各个模块首先是独立运行的线程
    • 模块在仿真一开始便并行执行,除了每个线程会按照自身内部产生的事件来触发过程语句块之外,也同时依靠相邻模块间的信号变化来完成模块之间的线程同步

    线程

    • 线程是可以独立运行的程序
    • 线程需要被触发,可以结束或者不结束
    • 在module中的 initial 和 always ,都可以看成独立的线程,他们在仿真 0 时刻开始,而选择结束或者不结束
    • 硬件模型中由于都是always 语句块,所以可以看成是多个独立运行的线程,而这些线程会一直占用仿真资源
    • 软件测试平台中的验证环境都需要有initial 语句块去创建,而在仿真过程中,验证环境中的对象可以创建和销毁,因此,软件测试端的资源占用是动态的
    • 线程的执行轨迹呈树状结构,任何线程都有父线程
      • 当子线程终止时,父线程可以继续执行 
      • 当父线程终止时,其所开辟的所有子线程都应当会终止 
    • 软件环境中的initial  块对语句有两种方式:
      • begin ...  end:内部语句以顺序方式执行
      • fork ... join:内部语句以并发方式执行

    线程的控制

    创建线程

    fork...join/join_any/join_none:   能够从它的每一个并行语句中产生并发进程。

    • join: 父进程阻塞到这个分支产生的所有进程结束;
    • join_any:  父进程阻塞到这个分支产生的任意一个进程结束;
    • join_none:    父进程会与其生成的所有子进程并发执行。在父进程执行一条阻塞语句或者结束之前,所生成的子进程不会启动执行。

     wait fork

    • 在SV中,当程序中的initial 块全部执行完毕,仿真器就退出了。
    • 如果需要等待fork 中的所有线程执行完毕再退出结束initial 块,可以使用 wait fork 语句来等待所有子线程结束
    initial begin: fork_join_none
      $display("@%0t, fork_join_none_thread entered", $time);
      fork: fork_join_none_thread
        thread(0, 10);   // 线程1
        thread(1, 20);   // 线程2
        thread(2, 30);   // 线程3
      join_none
      $display("@%0t, fork_join_none_thread exited", $time);
      wait fork;     // 等待所有线程退出
      $display("@%0t, fork_join_none_thread's all sub-threads finished", $time);
    end

    disable

    • 在使用fork ... join  或者fork ... join_none 以后,可以使用disable 来制定需要停止的线程
    • disable fork 可以停止从当前线程中衍生出来的所有子线程
    • 如果给某一个任务或者线程指明标号,那么当这个线程被调用多次以后,如果通过disable 去禁止这个线程标号,所有衍生的同名线程都将被禁止
    initial begin: fork_join_any
      $display("@%0t, fork_join_any_thread entered", $time);
      fork: fork_join_any_thread
        thread(0, 10);
        thread(1, 20);
        thread(2, 30);
      join_any 
      $display("@%0t, fork_join_any_thread exited", $time);
      disable fork_join_any_thread;
      $display("@%0t, disabled fork_join_any_thread", $time);
    end

    线程间的通信:IPC

    •  测试平台中的所有贤臣都需要同步并交换数据
    • 一个线程等待另一个线程
    • 多个线程可能同时访问同一个资源
    • 线程之间可能需要交换数据

    event

    • Verilog 中,一个线程总是要等待一个带 @ 操作符的事件。这个操作符是边沿敏感的,总是阻塞着、等待事件的变化
    • 其他线程可以通过  ->  操作法来触发事件,结束对第一个线程的阻塞
    • event 是边沿触发
    • 可以使用电平敏感的 wait(e1.triggered()) , 来代替边沿敏感的阻塞语句 @e1;只要event 被触发过,就可以防止引起阻塞
    • 明确什么时候使用wait:wait 用于等待一次,如果需要多次等待,则需要在等待到一次后,就清除一次,如uvm中的wait;@ 可以等待多次
    event e1, e2;
    initial begin
        ->e2;
        @e1;    //  触发e1,在event 边沿触发
    end
    initial begin
        ->e1;   //  等待e1
        @e2;
    end
    // e1, e2在同一时刻被触发,由于delta cycle的时间差,可能使得两个初始化无法等到e1 或者 e2
    
    // 推荐使用
    wait (e1.triggered());  

     线程间的通知

    class car;
        bit start = 0;
        task launch();
            start = 1;
            $display(car is launched);
        endtask
        task move();
            wait(start == 1);
            $display("car is moving");
        endtask
        task driver();
            fork
                this.launch();
                this.move();
            join
        endtask
    endclass
    
    module road;
        initial begin
            automatic car byd = new();
            byd.driver();
        end
    endmodule

    envet 模式

    class car;
        event     e_start;
        task launch();
            -> e_start;
            $display(car is launched);
        endtask
        task move();
            wait(e_start.triggered);
            $display("car is moving");
        endtask
        task driver();
            fork
                this.launch();
                this.move();
            join
        endtask
    endclass

     semaphore

    •  可以实现对同一资源的访问控制,互斥访问
    • semaphore 的三种基本操作:
      • new()  可以创建一个带单个或者多个钥匙的semaphore
      • get()    可以获取一个或者多个钥匙
      • put()    可以返回一个或者多个钥匙
    • try_get()  : 非阻塞式获取钥匙,函数返回 1 表示有足够多的钥匙,返回  0  表示钥匙不够

     mailbox信箱

    • 线程间如果传递消息,可以使用mailbox
    • mailbox和队列有相近之处
    • mailbox 是一种对象,需要使用 new() 例化;例化时有一个可选的参数 size 来限定其存储的最大数量
      • 如果size 是0 或者没有指定 new(),则信箱是无限大的,可以容纳任意多的条目
      • new(N), 设置容量为 N
    • put():可以把数据放入mailbox;如果信箱已满,则 put 会阻塞;try_put() 非阻塞方法
    • get():可以从信箱移除数据;如果信箱为空,则 get 会阻塞;try_get()  非阻塞方法
    • peek():可以获取对信箱里数据的拷贝而不移除它;try_peek 非阻塞方法
    • 线程之间的同步方法需要注意,哪些是阻塞方法,哪些是非阻塞方法
    • 如果要显式的限定mailbox 中的元素类型,可以通过mailbox #(type=T)  的方式来声明,例如 mailbox #(int)
    mailbox 队列
    必须通过new()例化 只需要声明队列
    可以同时存储不同的数据类型 内部存储的数据类型必须一致

    put 和 get 是阻塞方法

    调用阻塞方法只能在task中,因为阻塞是耗时的

    push_back 和pop_front是非阻塞的

    在使用queue 时,需要使用wait(queue.size()>0)才可以在其后对非空队列做取数据操作

    mailbox只能用作FIFO   queue 既可以用作FIFO, 也可以用作LIFO
    mailbox变量的操作,在传递形式参数是,实际传递并拷贝的是mailbox指针

    传递队列的形式参数默认是input方向,传递的是数组的拷贝;

    考虑使用ref方向,考虑对队列是引用还是拷贝

     总结

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

  • 相关阅读:
    APPCAN   版本控制SVN
    关于 java中的换行符
    BCompare中文版安装包
    netstat
    springboot mybatis generator
    mysql删除表的方式
    jdbc写入和读取过程
    hadoop全排序和二次排序
    mapreduce之数据倾斜
    hdfs切片的计算方式
  • 原文地址:https://www.cnblogs.com/gareth-yu/p/14618498.html
Copyright © 2020-2023  润新知