systemverilog绿皮书随记(六)--线程以及线程间的通信

7.1线程的使用

例7.9 在for 循环中便用了fork...Join_none.SystemVerilog 首先对fork.
Join_none里的线程进行调度,但是由于0时延的存在,这些线程要在原始代码块之后
执行,所以例7.9打印出来的是"333",即循环终止时家引变量j的值,

例7.9不良代码,在循环中内微fork..-join_none
program no_auto;
    initial begin
        for (int j=0; J< 3; j++ )
            fork
                $write(j);     //得到的是最终的索引值
                           
        join_none
      #0 $display("\n");
    end
endprogram


1 产生线程$write(j)[线程1]
2 j++    j=2
2 产生线程$write(j)[线程2]
3 j++    j=3
3 join_none
3 #0
3 $write (j)    [线程 0:j=3]
3 $write (j)    [线程 1:j=3]
3 $write (j)    [线程 2:j=3]
3 $display ()
0时延阻塞了当前线程,并且把它重新调度到当前时间片之后启动。

例7.11 fork...join_none里的自动变量
initial begin
for (int j = 0; j < 3; j++) 
  fork
        automatic int k = j; // 创建索引的拷贝
        $write(k); // 打印拷贝值
  join_none

    #0 $display; // 循环结束后打印
end

fork...join_none被分割成多个部分,带初始化的自动(automatic)变量声明
在for循环里的线程中运行,在每轮循环中,k的一个拷贝被创建并且被设置为j的当前值。然后fork...join_none($write)被调度,包括k的拷贝。在循环完成后,join_none阻塞了当前线程,因此三个线程一起运行,打印出各自拷贝值。当线程运行完毕后,在当前时间片已经没有其他事件残留,这时SystemVerilog就会前进到下一个语句执行$display。

例7.12 自动变量代码的执行步骤

j    | k    | 语句
-----|------|-----------------
0    | k0=0 | 创建k0,产生线程$write(k0)(线程0)
1    | -    | j++ 更新为1
1    | k1=1 | 创建k1,产生线程$write(k1)(线程1)
2    | -    | j++ 更新为2
2    | k2=2 | 创建k2,产生线程$write(k2)(线程2)
3    | -    | join_none
     | -    | $display(); // 所有线程执行完毕后打印

使用wait fork来等待所有子线程结束

停止单个线程:disable(使用时不用加冒号)

停止多个线程:用fork.. join把线程包起来,用disable停止

7.2事件event

@边沿敏感的阻塞语句

wait triggered电平敏感

如果在循环中使用wait triggered一定要确保在下次等待之前时间可以向前推进。

传递事件:

7.26
把事件传递给构造器
class Generator;
    event done;
    function new (event done);
        // 从测试平台中传来事件
        this.done = done;
    endfunction
    task run():
        fork
            begin
            // 创建事务
                ->done;
            // 告知测试程序任务已完成
            end
        join_none
    endtask
endclass

program automatic test;
    event gen_done;
    Generator gen;
    initial begin
        gen = new(gen_done);
        // 测试程序实例化
        gen.run();
        // 运行事务处理器
        wait(gen_done.triggered());
        // 等待任务结束
    end
endprogram

等待多个事件

例7.27 使用wait fork等待多个线程
event done[N_GENERATORS];
initial begin
    foreach (gen[i]) begin
        gen[i] = new(); // 创建N个发生器
        gen[i].run(done[i]); // 使它们开始运行
    end
    // 通过等待每个事件来等待所有发生器完成
    foreach (gen[i])
    fork
        automatic int k = i;
        wait (done[k].triggered());
    join_none
    wait fork; // 等待所有触发事件完成
end

例7.28 通过对触发事件进行计数来等待多个线程
event done[N_GENERATORS];
int done_count;
initial begin
    foreach(gen[i]) begin
        gen[i] = new();
        // 创建N个发生器
        gen[i].run(done[i]);
        // 使它们开始运行
    end
    // 等待所有发生器完成
    foreach (gen[i])
    fork
        automatic int k = i;
        begin
            wait (done[k].triggered());
            done_count++; // 增加完成计数
        end
    join_none
    wait(done_count == N_GENERATORS); // 等待触发计数等于发生器数量
end

例7.29 使用线程计数来等待多个线程

class Generator;
static int thread_count = 0;
task run();
    thread_count++;
    // 启动另一个线程
    fork
    begin
        // 这里省略实际工作的代码
        // 当工作完成时,对线程数目减计数
        thread_count--;
    end
    join_none
endtask
endclass

Generator gen[N_GENERATORS];
initial begin
    // 创建N个发生器
    foreach (gen[i])
        gen[i] = new();
    // 启动它们运行
    foreach (gen[i])
        gen[i].run();
    // 等待所有发生器完成
    wait (Generator::thread_count == 0);
end

7.3旗语semaphore

semaphore sem;//创建一个旗语

sem=new(1);//分配一个钥匙

使用旗语可以实现对同一资源的访问控制

在sv中,一个线程如果请求”钥匙”而得不到,则会一直阻塞,多个阻塞的线程会以先进先出的方式进行排队;

旗语的操作:new()可以创建带单个或者多个钥匙的旗语;使用get可以获得一个或者多个钥匙;put可以返回一个或者多个钥匙;如果试图获取一个旗语而希望不被阻塞,可以使用tyr_get()函数

7.4信箱mailbox

使用信箱可以在两个线程之间传递数据信息;

信箱是一种对象,必须调用new函数进行实例化可以给定信箱的大小,如果没有指定size或者是0,则信箱是无限大的

get()将数据从信箱中移除(无返回值);put()将数据放入信箱中(无返回值);peek可以获取信箱的数据的拷贝而不移除数据;try_put()和try_peek()不会发生阻塞(有返回值);

使用信箱和事件来实现线程的同步

例7.43 使用事件实现同步的生产方和消费方
program automatic mbx_evt;
mailbox mbx;
event handshake;
class Producer;
   task run;
   for (int i = 0; i < 4; i++) 
   begin
         $display("producer: before put (%d)", i);
             mbx.put(i);
             @handshake;
         $display("Producer: after put (%d)", i);
   end
endclass
// 下续例7.44
class Consumer;
   task run;
      int i;
      repeat (3) begin
         mbx.get(i);
         $display("Consumer: after get (%d)", i);
         ->handshake;
      end
   endtask
endclass Consumer

Producer p;
Consumer c;
initial begin
   mbx = new();
   handshake = event();
   c = new();
   // 使生产方和消费方并发运行
   fork
      p.run();
      c.run();
   join
end
endprogram



Producer: before put(1)
Consumer: after get(1)
Producer: after put(1)
Producer: before put(2)
Consumer: after get(2)
Producer: after put(2)
Producer: before put(3)
Consumer: after get(3)
Producer: after put(3)



使用两个信箱来实现线程的同步

例7.46 使用信箱实现同步的生产方和消费方
program automatic mbx_mbx2;
mailbox mbx, rtn;
class Producer;
   task run();
      int k;
      for (int i = 0; i < 4; i++) begin
         $display("Producer: before put(%d)", i);
         mbx.put(i);
         rtn.get(k);
         $display("Producer: after get(%d)", k);
      end
   endtask
endclass

class Consumer;
   task run();
      int i;
      repeat(3) begin
         $display("Consumer: before get");
         mbx.get(i);
         $display("Consumer: after get(%d)", i);
         rtn.put(-i);
      end
   endtask
endclass Consumer
Producer p;
Consumer c;
initial begin
   mbx = new();
   rtn = new();
   p = new();
   c = new();
   // 使生产方和消费方并发运行
   fork
      p.run();
      c.run();
   join
end
endprogram





例7.47 使用信箱实现同步的生产方和消费方的输出
Producer: before put (1)
Consumer: before get
Consumer: after get (1)
Consumer: before get
Producer: after get (-1)
Producer: before put (2)
Consumer: after get (2)
Consumer: before get
Producer: after get (-2)
Producer: before put (3)
Consumer: after get (3)
Producer: after get (-3)

// 生产方和消费方运行时取得了一致

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值