熟悉systemverilog的朋友应该都知道fork join_any disable有一个小陷阱,就是可能会误kill其他进程,所以需要一个guard fork。

突然奇想,看看哪个大模型可以不经过提醒(这个很重要,因为我们大概率不会把这个加入到提示词!)考虑到这个层面。

先说结论:deepseek v4 pro和claude opus 4.7胜出!

可以自行在chip.ai6800.com下调用模型做实验

GPT-5.4 -失败,花费0.0075

可以用 fork...join_any 配合 disable fork(或 disable )来实现。

推荐写法

task automatic run_taskA_taskB();
  fork : ab_fork
    begin : taskA_proc
      taskA();
    end

    begin : taskB_proc
      taskB();
    end
  join_any

  // taskA 和 taskB 任意一个先结束后,杀掉另外一个
  disable ab_fork;
endtask


更简洁写法

如果当前作用域明确,也可以这样写:

fork
  taskA();
  taskB();
join_any
disable fork;

不过在实际验证环境里,更推荐加名字,便于维护和调试:(其实还是有风险的!!!)

fork : ab_fork
  taskA();
  taskB();
join_any
disable ab_fork;

GPT 5.5 -失败,花费0.0205

task automatic taskA();
  $display("[%0t] taskA start", $time);

  // taskA执行内容
  #100;

  $display("[%0t] taskA done", $time);
endtask


task automatic taskB();
  $display("[%0t] taskB start", $time);

  // taskB执行内容
  #200;

  $display("[%0t] taskB done", $time);
endtask


task automatic run_taskA_taskB();
  fork : TASK_FORK
    begin
      taskA();
    end

    begin
      taskB();
    end
  join_any

  // taskA/taskB中任意一个完成后,终止另一个还在运行的task
  disable TASK_FORK;

  $display("[%0t] one task finished, remaining task disabled", $time);
endtask

说明:

  • fork ... join_any:并行启动 taskA 和 taskB,任意一个结束后继续往下执行。
  • disable TASK_FORK:关闭该 fork 块中仍在运行的其他线程。
  • 如果 taskA 先完成,则 taskB 会被终止。
  • 如果 taskB 先完成,则 taskA 会被终止。

Claude sonnet-4.6-失败,花费0.0646

这是一个使用SystemVerilog实现两个任务并行执行,任一完成后终止另一个的代码示例:

module parallel_task_example;
  
  // 任务A
  task automatic taskA();
    begin
      $display("[%0t] taskA started", $time);
      #100ns;  // 模拟任务A需要100ns完成
      $display("[%0t] taskA completed", $time);
    end
  endtask
  
  // 任务B
  task automatic taskB();
    begin
      $display("[%0t] taskB started", $time);
      #150ns;  // 模拟任务B需要150ns完成
      $display("[%0t] taskB completed", $time);
    end
  endtask
  
  // 主控制逻辑
  initial begin
    fork
      begin
        taskA();
      end
      begin
        taskB();
      end
    join_any  // 任何一个分支完成就继续执行
    
    // 终止所有未完成的并行进程
    disable fork;
    
    $display("[%0t] One task completed, both tasks disabled", $time);
    #10ns;
    $finish;
  end
  
endmodule

关键点说明:

  1. fork...join_any - 启动并行执行,任一分支完成后继续
  2. disable fork - 终止当前fork块中所有未完成的进程
  3. automatic - 确保任务可重入,支持并发调用

如果需要更精细的控制(比如只disable特定任务),可以使用命名的fork块:

initial begin
  fork : parallel_block
    begin
      taskA();
    end
    begin
      taskB();
    end
  join_any
  
  disable parallel_block;  // 通过名称disable特定fork块
  
  $display("[%0t] Tasks disabled", $time);
end

这段代码中,taskA会在100ns完成,此时taskB还在执行中(需要150ns),join_any会立即继续执行,然后disable fork会终止还在运行的taskB。

Claude opus 4.7-成功,花费0.0283

方法一:使用 fork...join_any + disable fork

module tb_top;

  // 定义 taskA
  task taskA();
    $display("[%0t] taskA started", $time);
    #100;  // 模拟一些耗时操作
    $display("[%0t] taskA completed", $time);
  endtask

  // 定义 taskB
  task taskB();
    $display("[%0t] taskB started", $time);
    #50;   // 模拟一些耗时操作(比taskA快)
    $display("[%0t] taskB completed", $time);
  endtask

  initial begin
    fork
      taskA();
      taskB();
    join_any

    // 任一 task 完成后,disable 掉 fork 内所有剩余线程
    disable fork;

    $display("[%0t] Both taskA and taskB have been disabled/finished", $time);
  end

endmodule

注意disable fork 会终止当前进程派生的所有子线程。如果调用环境中还有其他 fork 产生的线程,可能会被误杀。为了避免这个问题,推荐使用下面的方法二。


方法二:使用 fork...join_any + disable fork(加隔离层)

通过额外包裹一层 fork...join,将 disable fork的作用范围限定在内部,避免影响外部线程:

module tb_top;

  task taskA();
    $display("[%0t] taskA started", $time);
    #100;
    $display("[%0t] taskA completed", $time);
  endtask

  task taskB();
    $display("[%0t] taskB started", $time);
    #50;
    $display("[%0t] taskB completed", $time);
  endtask

  initial begin
    // 外层 fork...join 用于隔离 disable fork 的作用范围
    fork begin
      fork
        taskA();
        taskB();
      join_any
      disable fork;  // 只会 disable 内层 fork 派生的线程
      $display("[%0t] Both taskA and taskB have been disabled/finished", $time);
    end join

    $display("[%0t] Continue with other work...", $time);
  end

endmodule

方法三:使用命名 Block + disable

使用命名块可以精确控制要 disable 的目标:

module tb_top;

  task taskA();
    $display("[%0t] taskA started", $time);
    #100;
    $display("[%0t] taskA completed", $time);
  endtask

  task taskB();
    $display("[%0t] taskB started", $time);
    #50;
    $display("[%0t] taskB completed", $time);
  endtask

  initial begin
    fork : parallel_block
      begin : blk_a
        taskA();
      end
      begin : blk_b
        taskB();
      end
    join_any

    // 精确 disable 命名的 fork block
    disable parallel_block;

    $display("[%0t] Both taskA and taskB have been disabled/finished", $time);
  end

endmodule

仿真输出(以方法二为例)

[0] taskA started
[0] taskB started
[50] taskB completed
[50] Both taskA and taskB have been disabled/finished
[50] Continue with other work...

要点总结

方法 关键语法 优点 注意事项
方法一 join_any + disable fork 简洁 可能误杀其他线程
方法二 外层 fork begin...end join隔离 推荐,安全隔离 多一层嵌套
方法三 命名块 + disable block_name 精确控制 需要命名管理

实际项目中推荐方法二,既安全又清晰,是 UVM/验证环境中最常用的模式。

DeepSeek v4 pro-成功,花费0.0496

在芯片验证中,经常需要并行启动多个任务,并当其中任一任务完成时立即终止其余任务。SystemVerilog 中常见的实现方式有 fork...join_any + disable fork 以及基于 process 类的精细控制。下面给出两种典型写法,并说明各自的适用场景。


方法一:fork...join_any + disable fork(简洁、常用)

module tb;

  initial begin
    // 在独立的 begin...end 块内使用 fork,可限制 disable fork 的影响范围
    begin : isolated_fork_block
      fork
        taskA();
        taskB();
      join_any
      disable fork;   // 终止本块内所有活动的子进程(即 taskA / taskB)
    end

    $display("One of the tasks finished, the other is disabled.");
    // 继续后续测试逻辑...
  end

  // 示例 taskA(实际工作中可替换为真实的验证任务)
  task taskA();
    $display("taskA started");
    #10ns;            // 模拟耗时操作
    $display("taskA finished");
  endtask

  // 示例 taskB
  task taskB();
    $display("taskB started");
    #5ns;             // 模拟耗时更短的任务
    $display("taskB finished");
  endtask

endmodule

说明:

  • fork...join_any 等待 taskA 与 taskB 中任一完成即解除阻塞。
  • disable fork 会杀死当前线程(即 initial块)中所有尚未完成的子进程。为避免误杀同一 initial 块中的其他并行进程,通常把 fork 封装在一个独立的 begin...end 块中,并立即使用 disable fork
  • 优点:代码简洁,适合大多数验证场景。
  • 注意:如果 taskA / taskB 内部又开启了子 forkdisable fork 也会终止那些子孙进程(有时这正是期望的行为,有时则需要避免)。

方法二:使用 process 类精确控制(推荐用于复杂环境)

当需要精确地只终止 taskA 和 taskB,而不影响其他可能存在的并行进程时,可以保存进程句柄并单独 kill。

module tb;

  initial begin
    process pA, pB;

    fork
      begin
        pA = process::self();   // 获取当前进程句柄
        taskA();
      end
      begin
        pB = process::self();
        taskB();
      end
    join_any

    // 哪个没完成就 kill 哪个
    if (pA != null && pA.status != process::FINISHED)
      pA.kill();
    if (pB != null && pB.status != process::FINISHED)
      pB.kill();

    $display("One task completed, the other was killed precisely.");
  end

  task taskA();
    $display("taskA started");
    #10ns;
    $display("taskA finished");
  endtask

  task taskB();
    $display("taskB started");
    #5ns;
    $display("taskB finished");
  endtask

endmodule

说明:

  • process::self() 返回当前进程的句柄。
  • status 属性可以检查进程是否已经结束(FINISHEDKILLED 等)。
  • kill() 方法可以精确终止特定进程,不影响其他并行进程。
  • 优点:控制粒度细,不会误杀其他进程,适合复杂验证环境。
  • 要求:需要仿真器支持 IEEE 1800-2012 及以上标准。

总结

对于大多数芯片验证场景,方法一足够使用且书写快捷;当验证环境中有多个并行的 fork 或需要精细控制时,推荐采用方法二以避免副作用。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐