SV中 disable fork停止多个线程正确示例

根据上面的代码片段,并不会真的产生注释中所说的"线程0~4"这5个独立的线程 。注释中的编号是对线程概念的简化示意,但实际的行为与注释有较大差异。下面我会详细解释这段代码真实的线程创建与执行原理。


一、代码正确解读(去除注释的误导)

复制代码
initial begin
    check_trans(tr0);               // 步骤A
    fork                            // 步骤B:创建子线程
        begin
            check_trans(tr1);       // 步骤C
            fork                    // 步骤D:再创建一个子线程
                check_trans(tr2);   // 步骤E
            join                    // 等待步骤E的子线程结束
            #(TIME_OUT/2) disable fork; // 步骤F
        end
    join                            // 等待步骤B的子线程结束
end

二、真实产生的线程分析

1. 主线程(可以称为 thread_main

  • initial 块启动时,SV仿真器为主线程(即 initial 进程)分配一个执行线程。

  • 步骤Acheck_trans(tr0)主线程中顺序执行,不会创建新线程。

  • 步骤B :遇到 fork...join,主线程派生一个子线程 (称为 thread_A)来执行 begin...end 块,然后主线程阻塞join 处,等待 thread_A 结束。

✅ 此时存在的线程:thread_main(阻塞中)、thread_A(运行中)。

2. 子线程 thread_A

  • 步骤Ccheck_trans(tr1)thread_A 中顺序执行,不创建新线程。

  • 步骤D :遇到内部的 fork...jointhread_A 派生一个孙线程 (称为 thread_B)来执行 check_trans(tr2),然后 thread_A 阻塞 在内部的 join 处,等待 thread_B 结束。

✅ 此时存在的线程:thread_main(阻塞)、thread_A(阻塞)、thread_B(运行中)。

  • 步骤Ethread_B 执行 check_trans(tr2) 并结束。内部的 join 完成,thread_A 恢复执行。

  • 步骤Fthread_A 执行 #(TIME_OUT/2) disable fork;

disable fork 的作用
  • 它会终止当前线程(即 thread_A)的所有活跃子线程

  • 由于 thread_B 已经结束,此时没有活跃的子线程,所以这条语句没有任何效果。

  • 重要disable fork 不会 终止 thread_A 自身,也不会影响它的父线程 thread_main

  • thread_A 执行完 disable fork 后,begin...end 块结束,thread_A 退出。

  • 外层的 join 感知到 thread_A 结束,主线程 thread_main 恢复执行,然后 initial 块结束。


三、为什么注释中说"线程0~4"?

注释中的编号是一种不严谨的标记

  • 线程0:主线程(实际存在)。

  • 线程1 :外层 fork 创建的子线程 thread_A(实际存在)。

  • 线程2check_trans(tr1) 被误认为独立线程 ------ 它只是 thread_A 内的一个函数调用。

  • 线程3 :内层 fork 本身被误认为线程 ------ fork 语句不创建自身线程,它是派生动作。

  • 线程4check_trans(tr2) 被误认为独立线程 ------ 它实际是 thread_B 内的函数调用。

所以,真实存在的独立线程最多只有3个 :主线程、外层 fork 的子线程、内层 fork 的孙线程。而且由于内层 fork...join 会等待孙线程结束,这三个线程的执行是近乎顺序的(主线程等待子线程,子线程等待孙线程),不会有三个线程同时并行执行的情况。


四、结论

  • 不会产生5个独立线程,注释中的编号是错误的。

  • 原理fork...join 创建新线程;函数调用不创建线程;disable fork 只终止当前线程的子线程,不影响自身及父线程。

  • 如果想要"停止多个线程",需要注意 disable fork 的作用范围,并且通常需要配合 fork...join_none 或适当的线程组织才能实现真正的并发控制。

下面给出一个能够实现"停止线程1~4,单独保留线程0"的正确 SystemVerilog 代码示例,并详细解释其原理。


✅ 正确的示例代码

复制代码
module top;
    // 模拟一个耗时检查任务
    task check_trans(int id);
        $display("[%0t] Thread %0d started", $time, id);
        #100;
        $display("[%0t] Thread %0d finished", $time, id);
    endtask

    initial begin
        // 线程0(主线程)
        $display("[%0t] Main thread (thread 0) started", $time);

        // 创建4个并发子线程(线程1~4)
        fork
            check_trans(1);   // 线程1
            check_trans(2);   // 线程2
            check_trans(3);   // 线程3
            check_trans(4);   // 线程4
        join_none           // 主线程不等待子线程,继续执行

        // 主线程继续执行其他任务(比如监控)
        #50;                // 让子线程运行50时间单位

        // 停止所有从当前线程(主线程)派生的子线程
        $display("[%0t] Disabling all forked threads...", $time);
        disable fork;       // 杀死线程1~4

        // 验证子线程已被停止
        #200;
        $display("[%0t] Main thread finished", $time);
    end
endmodule

可能的输出结果(时间单位视仿真器而定):

复制代码
[0] Main thread (thread 0) started
[0] Thread 1 started
[0] Thread 2 started
[0] Thread 3 started
[0] Thread 4 started
[50] Disabling all forked threads...
[250] Main thread finished

可以看到,子线程在 #50 时被终止,没有输出 "finished" 信息。


🔍 原理详解

1. 线程编号对照

代码实体 注释中的线程编号 实际线程类型
initial 块整体 线程0 主线程(父线程)
fork...join_none 内的四个 check_trans 调用 线程1~4 主线程的子线程

2. fork...join_none 的作用

  • 它会立即 派生所有子线程(每个 check_trans(id) 成为一个独立并发线程)。

  • 主线程不等待 子线程结束,直接继续执行后续代码(#50disable fork)。

  • 如果没有 join_none,而是使用 joinjoin_any,主线程就会被阻塞,无法在子线程运行中途执行 disable fork

3. disable fork 的作用

  • 该语句终止调用它的线程的所有活跃子线程

  • 此处主线程调用 disable fork,因此主线程的所有子线程(线程1~4)都会被立即终止。

  • 主线程自身不会被终止disable fork 只影响后代,不影响自身。

  • 已经被终止的线程不会执行剩余的代码(如 #100; $display(...finished))。

4. 为什么能"单独保留线程0"?

因为 disable fork 只杀死了从主线程派生出来的子线程,主线程本身继续运行,并执行了最后的 #200$display


🔧 扩展:更精细的控制(只停止部分子线程)

如果需求是"只停止某几个子线程,保留其他子线程继续运行",可以使用 process (SystemVerilog 2012+)或 fork...join 配合命名块

示例:使用 process 类停止特定线程

systemverilog

复制代码
process p1, p2, p3, p4;

initial begin
    fork
        begin : t1 p1 = process::self(); check_trans(1); end
        begin : t2 p2 = process::self(); check_trans(2); end
        begin : t3 p3 = process::self(); check_trans(3); end
        begin : t4 p4 = process::self(); check_trans(4); end
    join_none

    #50;
    p1.kill();   // 只停止线程1
    p2.kill();   // 只停止线程2
    // p3, p4 继续运行
end

📌 关键注意事项

  1. disable fork 作用范围 :它会杀死当前线程的所有子线程 ,包括嵌套派生出来的孙线程。在上述例子中,因为子线程内部没有再次 fork,所以影响范围正好是线程1~4。

  2. 不要在主线程的多个位置随意使用 disable fork :如果你在主线程中再次 fork 了新任务,后面再调用 disable fork 也会杀死它们。通常建议将需要受控的子线程集中在一个 fork...join_none 块中。

  3. 时序控制 :需要确保 disable fork 在子线程执行完之前发生,否则子线程可能已经自然结束,此时 disable fork 没有效果(但也不会出错)。


💎 总结

  • 原始图片代码 的问题:使用 fork...join 导致顺序执行,disable fork 作用范围错误,无法实现"停止多个线程"。

  • 正确做法 :使用 fork...join_none 创建并发子线程,然后在需要的时间点由主线程执行 disable fork 来终止它们。

  • 原理核心fork...join_none + disable fork 是 SystemVerilog 中实现"超时控制"或"抢先停止子任务"的经典模式。

相关推荐
liuluyang5306 小时前
SV中if与iff区别与用法
fpga开发·sv
liuluyang5304 天前
SV 时钟移位示例代码解析
fpga开发·sv
liuluyang5305 天前
SV 移位寄存器操作
sv
liuluyang5301 个月前
SV主要关键词详解
fpga开发·uvm·sv
北方孤寂的灵魂5 个月前
systemverilog中随机std::randomize的用法
verilog·systemverilog·sv·数字验证
啄缘之间1 年前
17. 示例:用assert property检查FIFO空满标志冲突
学习·fpga开发·verilog·uvm·sv
啄缘之间1 年前
7. 覆盖率:covergroup/coverpoint/cross
学习·测试用例·verilog·uvm·sv
啄缘之间1 年前
4. 示例:创建带约束的随机地址生成器(范围0x1000-0xFFFF)
学习·测试用例·verilog·uvm·sv
啄缘之间1 年前
4.6 学习UVM中的“report_phase“,将其应用到具体案例分为几步?
学习·verilog·uvm·sv