systemverilog中for/foreach并行执行

for-join_none并行

for循环fork-join_none语句可以组合使用来并行执行多个块,这里必须使用非阻塞的fork-join_none来启动多线程,因为使用fork-join_none时每一次循环都会创建新的fork块,并且不影响之后创建fork块,而fork-join则会阻塞后面的fork块的执行,并且保证fork块内的线程并行执行。但其实在使用时,很容易出错,下面先描述一种常见错误情况。

program no_auto;
  initial begin
  for(int j=0;j<3;j++)
    fork
      $write(j);
    join_none
  #0 $display("\n");
  end
endprogram

上面代码本意时每个线程依次打印0,1,2,但当运行消耗时间之前需要在不消耗时间的函数在#0时延之前计算完成。因此依次执行下面代码

  1. j=0 创建write(j)——线程0,创建不执行
  2. j=1 创建write(j)——线程1,-创建不执行
  3. j=2 创建write(j)——线程2,-创建不执行
  4. #0 执行消耗时间前,多线程同时执行
  5. j=2 执行write(j)——线程0
  6. j=2 执行write(j)——线程1
  7. j=2 执行write(j)——线程2
  8. $display("\n")——主线程执行

而由于多线程之间使用的时同一个变量,则导致线程0、1、2打印的都为j=2.
因此为了避免这种错误,可以使用自动变量来保存变量的拷贝,之后每一个线程都会创建自动变量k并保存一次j的值,#0之后三个线程将打印出其拷贝值k,而不再是共同且唯一的变量j。

program no_auto;
  initial begin
  for(int j=0;j<3;j++)
    //当然也可以放在这里
    //automatic int k=j;
    fork
      automatic int k=j;
      $write(k);
    join_none
  #0 $display("\n");
  end
endprogram

当然如果代码是在automatic类型的代码或者模块里面,那么声明时可以不适用关键词automatic声明变量,而是可以直接在循环中使用变量,即可自动在每个线程中创建变量。

program automatic auto;//通过指定代码块为自动变量存储,此时在for循环中的k
  initial begin					//在每一次循环中都会给定不同的存储空,此时多次调用
    for(int j=0;j<3;j++)//将不会存在问题
    int k=j;
    fork
      $write(k);
    join_none
  #0 $display("\n");
  end
endprogram

但在UVM中是不是需要必须指明automatic代码块呢?
答案是不一定的,因此SV中的class的方法就默认是automatic模式,因此不需要特别的在fork前面的外部自动变量做automatic int k=j的声明,直接按照变量声明即可 int k=j,但为保证含义清晰,最好添加上automatic

foreach并行

foreach相比于for循环,其需要输入数组变量,foreach(变量[迭代器]]),输入变量少,而且可以更方便的迭代。常见用法包括可以在UVM环境中将并行启动多个seqence,将seqence同时发送到多个agent上。


  begin : foreach_fork
    seq_class seq [`CONST];
    foreach(env.agt[i])
      begin
        automatic int j = i;
          seq[j] = seq_class::type_id::create
                  (.name($sformatf("seq_%0d", j)), .contxt(get_full_name()));
        fork
          begin
            seq[j].start(env.agt[j].sqr);
          end
        join_none // non-blocking thread
      end
    wait fork; //等待
  end : foreach_fork  

具体执行过程如下:

  1. foreach循环遍历每一个env.agt集合的每个代理。
  2. automatic int j = i;创建为唯一的索引,对于避免并发问题,这里相当重要,由于这里是类的方法,所以可以不加automatic而单纯是int j=1,并创建对应的sequence.
  3. 使用 fork-join_none 进行并行化启动,此在fork-join_none中新线程来并执行其中的代码块,此关键字指示仿真器继续执行,而无需等待已经启动的线程完成,这允许循环继续迭代并为其他代理启动新线程。
  4. seq.start(env.agt[j].sqr):此行使用sequence在env.agt[j].sqr上启动。
  5. wait fork:所有线程启动完成之后,并在此行之前同时执行。此语句使主线程等待,直到所有先前fork的线程(来自foreach)完成执行,这确保所有线程的sequence发送完成。

参考文献
[1]How can I use foreach and fork together to do something in parallel?
[2] 【system verilog】fork-join_none与循环语句共同使用的行为探究_fork join none-CSDN博客
[3] 绿皮书

posted @ 2024-08-04 22:56  NullBeer  阅读(335)  评论(0)    收藏  举报