SV学习笔记(十二)-线程控制

线程控制

并行线程

  • fork…join 需要所有并行的线程都结束以后才会继续执行

  • fork…join_any 等到任何一个线程结束后继续执行

  • fork…join_none 不会等待子线程,直接继续往下执行

    fork
        begin
            $display("First Block\n");
            # 20ns;
        end
        begin
            $display("Second Block\n");
            @eventA;
        end
    join
    
  • Verilog中与顺序线程begin…end相同的是并行线程fork…join

  • SV引入了两种新的创建线程的方式,fork…join_any和fork…join_none

    在这里插入图片描述

  • 示例

    task automatic my_func_fork(int id, int t);
    		$display("func_id is %0d, start.....", id);
    		#(t*10ns);
    		$display("func_id is %0d, end.....", id);
    	endtask
    

    不加线程

    $display("start......");
    my_func_fork(1, 10);
    my_func_fork(2, 20);
    my_func_fork(3, 30);
    my_func_fork(4, 40);
    $display("end......");
    // 输出结果
    # start......
    # func_id is 1, start.....
    # func_id is 1, end.....
    # func_id is 2, start.....
    # func_id is 2, end.....
    # func_id is 3, start.....
    # func_id is 3, end.....
    # func_id is 4, start.....
    # func_id is 4, end.....
    # end......
    

    fork…join

    $display("start......");
    fork
        my_func_fork(1, 10);
        my_func_fork(2, 20);
        my_func_fork(3, 30);
        my_func_fork(4, 40);
    join
    $display("end......");
    // 输出结果
    # start......
    # func_id is 1, start.....
    # func_id is 2, start.....
    # func_id is 3, start.....
    # func_id is 4, start.....
    # func_id is 1, end.....
    # func_id is 2, end.....
    # func_id is 3, end.....
    # func_id is 4, end.....
    # end......
    

    fork…join_any

    $display("start......");
    fork
        my_func_fork(1, 10);
        my_func_fork(2, 20);
        my_func_fork(3, 30);
        my_func_fork(4, 40);
    join_any
    $display("end......");
    // 输出结果
    # start......
    # func_id is 1, start.....
    # func_id is 2, start.....
    # func_id is 3, start.....
    # func_id is 4, start.....
    # func_id is 1, end.....
    # end......
    # func_id is 2, end.....
    # func_id is 3, end.....
    # func_id is 4, end.....
    

    fork…join_none

    $display("start......");
    fork
        my_func_fork(1, 10);
        my_func_fork(2, 20);
        my_func_fork(3, 30);
        my_func_fork(4, 40);
    join_none
    $display("end......");
    // 输出结果
    # start......
    # end......
    # func_id is 1, start.....
    # func_id is 2, start.....
    # func_id is 3, start.....
    # func_id is 4, start.....
    # func_id is 1, end.....
    # func_id is 2, end.....
    # func_id is 3, end.....
    # func_id is 4, end.....
    
  • fork…join_any和fork…join_none,会有未完成的子线程仍在后台运行

  • wait fork等待子线程全部完成

  • disable fork停止未完成的子线程

时序控制

  • SV可以通过延时控制或者事件等待来对过程块完成时序控制

  • 延迟控制即通过#来完成

    #10;
    
  • 事件(event)控制即通过@来完成

    @(posedge clock);
    
  • wait语句也可以与时间或者表达式结合来完成

    real AOR[];
    initial wait(AOR.size() > 0) ......;
    

进程间的同步和通信

  • 测试平台中的所有线程都需要同步并交换数据
  • 一个线程等待另一个,例如验证环境需要等待所有激励结束,比较结束才可以结束仿真
  • 比如监测器需要监测到的数据发送至比较器,比较器又需要从不同的缓存获取数据进行比较

事件event

  • 可以通过event来声明一个命名event变量并且去触发它

  • 这个命名event可以用来控制进程的执行

  • 可以通过->来触发事件

  • 其他等待该事件的进程可以通过@操作符或者wait()来检查event触发状态来完成

    • @边沿触发
    • wait()电频触发
    `timescale 1ns/1ps
    module tb;
        event e1,e2,e3;	// 声明三个事件
        task automatic wait_event(event e, string name);
            $display("@%t start waiting event %s", $time, name);
            @e;	// 等待事件  方式一
            wait(e.triggered);	// 等待事件  方式二
            $display("@%t end waiting event %s", $time, name);
        endtask
        
        initial begin
        	fork
                wait_event(e1, "e1");	// 	调用任务
                wait_event(e2, "e2");
                wait_event(e3, "e3");
            join
        end
        
        initial begin
            fork
                begin #10ns -> e1; end	// 触发事件
                begin #20ns -> e2; end
                begin #40ns -> e3; end
            join
        end
    endmodule
    // 执行结果
    # @                   0 start waiting event e1
    # @                   0 start waiting event e2
    # @                   0 start waiting event e3
    # @               10000 end waiting event e1
    # @               20000 end waiting event e2
    # @               40000 end waiting event e3
    

    如果等待的是信号而非事件

    `timescale 1ns/1ps
    module tb;
        bit e1,e2,e3;	// 声明三个比特信号
        task automatic wait_event(ref bit e, input string name);	// 这里要监控e的变化,因此要用ref
            $display("@%t start waiting event %s", $time, name);
            @e;	// 监测变化
            $display("@%t end waiting event %s", $time, name);
        endtask
        
        initial begin
        	fork
                wait_event(e1, "e1");	// 	调用任务
                wait_event(e2, "e2");
                wait_event(e3, "e3");
            join
        end
        
        initial begin
            fork
                begin #10ns e1 = !e1; end	// bit是二值逻辑,初值为0,取反变为1
                begin #20ns e2 = !e2; end
                begin #40ns e3 = !e3; end
            join
        end
    endmodule
    
  • wait_order()可以是的进程保持等待,直到再参数列表中的事件event按照顺序从左到右依次完成

  • 如果参数列表中的事件被触发但是没有按照要求的顺序,那么会使得等待操作失败

    wait_order(a, b, c);
    
    wait_order(a, b, c) else $display("Error: event out of order");
    
    bit success;
    wait_order(a, b, c) success = 1; else success = 0;
    

旗语(semaphore)

  • 旗语从概念上讲,是一个容器

  • 在创建旗语的时候,会为其分配固定的钥匙数量

  • 使用旗语的进程必须先获得其钥匙,才可以继续执行

  • 旗语的钥匙数量可以有多个,等待旗语钥匙的进程也可同时有多个

  • 旗语通常用于互斥,对共享资源的访问控制,以及基本的同步

  • 创建旗语并为其分配钥匙的方式如下:

    • 创建旗语,并为其分配钥匙

      semaphore sm;
      sm = new(x);	// 有多少钥匙x就给多少
      
    • 创建一个有固定钥匙数量的旗语:new(N)

    • 从旗语那里获取一个或多个钥匙(阻塞型):sm.get(N)

    • 将一个或多个钥匙返回到旗语中:sm.put(N)

    • 尝试获取一个或多个钥匙不会阻塞(非阻塞型):sm.try_get(N)

信箱(mailbox)

  • 信箱mailbox可以使得进程之间的信息得以交换,数据可以由一个进程写入信箱,再由另一个进程获取

  • 信箱在创建时可以限制容量,也可以不限制

  • 当信箱容量写满时,后续再写入的动作会被挂起,直到信箱中数据被读取,使得信箱有空间以后才可以继续写入

  • 不限制容量的信箱则不会挂起写入信箱的动作

  • 创建信箱

    在这里插入图片描述

  • 信箱的内建方法

    • 创建信箱:new()

    • 将信息写入信箱,信箱满了会阻塞:put(data),将data写到信箱中

    • 试着写入信箱,不会阻塞:try_put()

    • 从信箱中取出数据:get(data),从信箱中取出数据赋值给data

    • 信箱数据快照,能拿到信箱内的数据,但信箱内的数据不会减少:peek()

    • 试着读取数据:try_get()、try_peek()

    • 获取信箱信息的数据数目:num()

    • 可以结合num()与get()或者put(),防止信箱为空或者为满的时候阻塞

      mailbox #(int) mb;	// 定义一个信箱,#(int)表示只能存储int类型的数据
      initial begin
          int val;	// 定义一个int类型变量
          mb = new(8);	// 例化信箱,指定可存储数据8个
          forever begin
              case($urandom() % 2)
                  0: begin
                      val = $urandom_range(0,10);
                      mb.try_put(val);
                      $display("mb put value is %0d", val);
                  end
                  1: begin
                      mb.try_get(val);
                      $display("mb get value is %0d", val);
                  end
              endcase
          end
      end
      
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值