线程间同步和通信,event semaphore mailbox

本文详细介绍了线程间的同步和通信技术,包括事件(event)、旗语(semaphore)和信箱(mailbox)的使用。事件通过wait_order()实现顺序控制,旗语提供钥匙管理实现资源互斥访问,信箱则用于进程间的数据交换。通过示例代码展示了如何在Verilog中应用这些机制,确保线程安全和数据一致性。

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

1. 概述

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

2. 事件event

  • 可以通过event来声明一个命名event变量,并且去触发它
  • 这个命名event可以用来控制进程的执行
  • 可以通过->来触发事件
  • 其他等待该事件的进程可以通过@操作符或者wait()来检查event触发状态来完成
`timescale 1ns/1ns
module tb7;
  event e1, e2, e3;

  task automatic wait_event(event e, string name);
    $display("@%t start waiting event %s", $time, name);
    @e;
    $display("@%t finish 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 #30ns -> e3; end
    join
  end

endmodule

在这里插入图片描述

module tb8;
  bit e1, e2, e3;

  task automatic wait_event(ref bit e, input string name);
    $display("@%t start waiting event %s", $time, name);
    @e;
    $display("@%t finish 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
    $display("before fork, e1 = %x", e1);
    fork
      begin #10ns e1 = !e1; end
      begin #20ns e2 = !e2; end
      begin #30ns e3 = !e3; end
    join
    $display("after fork, e1 = %x", e1);
  end

endmodule

在这里插入图片描述

3. wait_order()

  • wait_order可以使得进程保持等待,直到在参数列表中的事件event按照顺序从左到右依次完成
  • 如果参数列中的事件被触发但是没有按照要求的顺序,那么使得等待操作失败
wait_order(a, b, c);

wait_order(a, b, c) else $display("Error: events out of order");

bit success;
wait_order(a, b, c) success = 1; esle success = 0;

【Q】下面关于event的说法哪些是正确的?
A: event之间也可以拷贝
B: 被拷贝的event与拷贝的event指向同一个event
C: 如果event先发生,再@event,不会被阻塞
D: 如果event先发生,再wait(event.triggered),不会被阻塞

【An】
event之间做拷贝,拷贝的是句柄
@是边沿触发
wait是电平触发

4. 旗语(semaphore)

  • 旗语从概念上讲,是一个容器
  • 在创建旗语的时候,会为其分配固定的钥匙数量
  • 使用旗语的进程必须先获得其钥匙,才可以继续执行
  • 旗语的钥匙数量可以有很多个,等待旗语钥匙的进程也可同时有很多个
  • 旗语通常用于互斥,对共享资源的访问控制,以及基本的同步
  • 目的是为了保护共享资源,规定访问资源的钥匙·1

  • 创建旗语,并为其分配钥匙,
    semaphore sm;
    sm = new();
  • 创建一个具有固定钥匙数量的旗语,
    new (N = 0)
  • 从旗语那里获取一个或多个钥匙(阻塞型),
    get (N = 1)
  • 将一个或多个钥匙返回到旗语中,
    put (N = 1)
  • 尝试获取一个或多个钥匙而不会阻塞(非阻塞型),
    try_get (N = 1)
module tb9;
  semaphore mem_acc_key;

  int unsigned mem[int unsigned];

  task automatic write_mem(int unsigned addr, int unsigned data);
      mem_acc_key.get();
      #1ns;
      mem[addr] = data;
      mem_acc_key.put();
  endtask

  task automatic read_mem(int unsigned addr, output int unsigned data);
      mem_acc_key.get();
      #1ns;
      if(mem.exists(addr))
        data = mem[addr];
      else
        data = 'x;
      mem_acc_key.put();
  endtask

  initial begin
    int unsigned data = 100;
    mem_acc_key = new(1);
    forever begin
      fork
        begin
          #10ns;
          write_mem('h10, data+100);
          $display("@%t write with data %d", $time, data);
        end
        begin
          #10ns;
          read_mem('h10, data);
          $display("@%t read  with data %d", $time, data);
        end
      join
    end
  end
endmodule

在这里插入图片描述

5. semaphore::get();

  • get()方法用于从旗语中获取指定数量的钥匙
  • get()原型,
    task get(int ketCount = 1);
  • keyCount指定从旗语获取所需的钥匙数,默认值为1
  • 如果指定数量的钥匙可用,则该方法返回并继续执行
  • 如果指定数量的钥匙不足,进程将阻塞,知道钥匙数目充足
  • 旗语的等待队列是先进先出(FIFO),即先排队等待旗语的将优先得到钥匙

6. semaphore::try_get()

  • try_get()方法用于从信号量中获取指定数量的钥匙,但不会被阻塞
  • try_get()原型,
    function int try_get(int keyCount = 1);
  • keyCount指定从旗语处获取所需的钥匙数目,默认值为1
  • 如果指定数量的钥匙可用,则该方法返回正数并继续执行
  • 如果指定数量的钥匙不足,则该方法返回0

7. 信箱mailbox

  • 信箱mailbox可以使得进程之间的信息的以交换,数据可以由一个进程写入信箱,再又另外一个进程获得
  • 信箱在创建时可以限制其容量,或者不限制
  • 当 信箱容量写满时,后续再写入的动作会被挂起,直到信箱的数据从中读取,使得信箱有空间以后才可以继续写入
  • 不限制容量的信箱则不会被挂起写入信箱的动作

7.1. 信箱的内建方法

  • 创建信箱:new()
  • 将信息写入信箱:put()
  • 试着写入信箱但不会阻塞:try_put()
  • 获取信息:get()同时会取出数据,peek()不会取出数据
  • 试着从信箱中取出数据但不会阻塞:try_get() / try_peek()
  • 获取信箱信息的数据:num()

7.2. mailbox::new()

  • 可以在创建信箱的时候限定或者不限定其大小,
    function new(int bound = 0);
  • 默认情况下,如果不传入参数,bound默认值为0,表示不限定信箱大小,如果传入的数值大于0,那么表示信箱的最大容量
  • bound应为正数,如果为负数的话,系统会提示警告和出现无预期的行为

7.3. mailbox::num()

  • num()会但会信箱目前的消息数目
  • 可以结合num()和get()或者put(),防止get() / put()方法在信箱为空或者为满的时候被阻塞
module tb10;
  mailbox #(int) mb;

  initial begin
    int data;
    mb = new(8);
    forever begin
      case($urandom_range(0, 1))
        0: begin
          if(mb.num() < 8) begin
            data = $urandom_range(0, 10);
            mb.put(data);
           $display("mb put data %0d", data);
         end
        end
        1: begin
          if(mb.num() > 0) begin
            mb.get(data);
            $display("mb get data %0d", data);
          end
        end
      endcase
      #1ns;
    end
  end
endmodule

在这里插入图片描述

7.4. 参数化信箱

  • 默认的信箱,在没有指定存储类型的情况下,可以存储任何类型的数据(程序也可能会为此受伤)
  • 为了避免运行时错误和类型不匹配,建议在声明信箱的时候为其指定存储类型

【Q】关于信箱的说法,正确的是?
A: 可以指定信箱存储的数据类型
B: 可以不限制信箱的容量大小
C: 在信箱写满时,继续写入,数据会溢出丢失
D: 在信箱为空时,调用get()方法将返回数值0

【An】
信箱写满继续写会等待
信箱为空,用get()会阻塞,用try_get()返回0

<think>我们正在讨论SystemVerilog中的线程间通信机制:mailboxeventsemaphore。用户希望了解它们的区别。根据引用内容,我们可以总结如下:1.**mailbox**:用于进程/线程间的数据交换。它是一个内置类,类似于队列,但只能按照FIFO顺序存取数据(引用[1][2])。提供的方法包括:new(),put(),try_put(),get(),peek(),try_get(),try_peek(),num()等。mailbox可以存储任意SystemVerilog数据类型,并通过内部信号量机制控制存取同步(引用[2])。2.**event**:用于进程/线程间的简单同步。事件可以触发(->)或等待(@或wait())。例如,一个线程触发事件,另一个线程等待该事件(引用[4])。事件没有数据传递能力,仅用于通知。3.**semaphore**:用于控制对共享资源的访问。可理解为“钥匙计数器”:初始化时指定钥匙数量,使用资源前必须获取钥匙(get()),使用后归还钥匙(put())(引用[3])。例如,初始化1把钥匙的semaphore可实现互斥访问。**核心区别**:-**数据传递**:mailbox可传递数据;eventsemaphore仅用于同步(无数据传递)。-**同步机制**:-event:简单事件通知(一方触发,另一方等待)。-semaphore:资源计数(控制共享资源访问)。-mailbox:内置同步机制(存取阻塞/非阻塞方法)。-**应用场景**:-需传递数据→mailbox。-仅需通知同步event。-控制共享资源(如共享内存)→semaphore。**示例对比**:1.**mailbox**:生产者线程put数据,消费者线程get数据。2.**event**:线程A触发事件e,线程B等待e触发后继续执行。3.**semaphore**:初始化1把钥匙,线程访问临界区前get钥匙,访问后put钥匙。现在生成相关问题:§§相关问题§§1.SystemVerilogmailbox的阻塞非阻塞方法有什么区别?2.如何用event实现两个线程的同步?3.semaphore在哪些场景下比mailbox更合适?</think>### SystemVerilogmailboxeventsemaphore的区别 在SystemVerilog中,mailboxeventsemaphore都是用于线程间通信同步的关键机制,但它们的应用场景工作原理有显著差异: #### 1. **mailbox(邮箱)** - **作用**:进程间**安全传递数据**的通道(支持任意数据类型) - **工作原理**: - 本质是**带同步机制的FIFO队列**(引用[2]) - 通过阻塞/非阻塞方法实现存取同步: ```systemverilog mailbox mb = new(); mb.put(data); // 阻塞式写入 mb.get(data); // 阻塞式读取 ``` - **关键特性**: - 数据传递:可传输类对象、结构体等复杂数据 - 同步方式:内置信号量控制存取时序(引用[2]) - 容量管理:`num()`查询消息数量,满/空时自动阻塞 - **典型应用**:生产者-消费者模型,如测试平台(DUT)与验证组件(TB)的数据交换(引用[1]) #### 2. **event(事件)** - **作用**:线程间**简单状态通知** - **工作原理**: - 基于事件触发(`->`)与等待(`@`或`wait()`): ```systemverilog event done; thread1: -> done; // 触发事件 thread2: @(done); // 阻塞等待 ``` - **关键特性**: - **无数据传递**:仅传递事件发生信号 - 瞬时性:未等待的事件触发会丢失(需用`wait(done.triggered)`) - 多线程同步:支持多个线程等待同一事件(引用[4]) - **典型应用**:控制流程同步,如"停车熄火前检查挡位"场景(引用[4]) #### 3. **semaphore(信号量)** - **作用**:**共享资源访问控制** - **工作原理**: - 基于"钥匙计数器"模型: ```systemverilog semaphore sem = new(1); // 初始化1把钥匙 sem.get(); // 获取钥匙(无钥匙时阻塞) sem.put(); // 归还钥匙 ``` - **关键特性**: - **资源管理**:控制对共享资源(如内存、外设)的并发访问 - 无数据传递:仅管理资源访问权限 - 非阻塞操作:`try_get()`避免死锁(引用[3]) - **典型应用**:保护共享资源,如多线程访问同一缓存(引用[3]) --- ### 核心区别总结 | 特性 | mailbox | event | semaphore | |---------------|-----------------|-----------------|-----------------| | **主要用途** | 数据传递 | 事件通知 | 资源访问控制 | | **数据传输** | 支持任意数据类型| ❌ 无数据 | ❌ 无数据 | | **同步机制** | 内置FIFO+信号量 | 触发/等待 | 钥匙计数器 | | **阻塞行为** | put/get阻塞 | @等待阻塞 | get阻塞 | | **典型场景** | 进程间数据交换 | 简单状态同步 | 共享资源保护 | > 示例场景对比: > - 用**mailbox**传递DUT的测试数据包(引用[1]) > - 用**event**同步"熄火前检查挡位"操作(引用[4]) > - 用**semaphore**控制共享内存的读写(引用[3])
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值