systemverilog线程及线程间的通信(一)

实际硬件中,计算是并发进行的,在Verilog中通过initial、always、连续赋值来模拟,在测试平台中为了模拟、检验Verilog中的这些语句块,tb使用许多并发的线程。

1. 线程的定义和使用

1.1 定义线程

initial 、always、assign都是进程,初次之外还有:

  1. fork join

    其内的语句并发,fork-join块执行完才执行后面的语句。

  2. fork join_none

    其内 的语句并发,并且不会阻塞块之后的语句,块内语句与块之后的语句是并发关系。

initial begin
    语句1;
    #10;
    fork
        语句2;
        语句3;
    join_none
    语句4;
    语句5;
end
// 执行顺序
// #0 语句1
// #10 语句2,语句3,语句4并发执行
// #10 语句4执行完之后才执行语句5。4执行完之后,即使2,3没执行完,也会接着执行5,因为fork块内语句与之后的语句是并行的,不会阻塞之后的语句
  1. fork join_any

    与fork Join_none类似,只是,先要执行完一条块内的进程,才会继续执行fork..join_any后的语句块。

initial begin
  fork
      #20 $display("@ %0t [line1] seq 20",$time);
      #10 $display("@ %0t [line2] seq 10",$time);
  join_any
    #15  $display("@ %0t [line3] after seq 15",$time);
end
//结果
@ 10 [line2] seq 10
@ 20 [line1] seq 20
@ 25 [line3] after seq 15 

分析:先要执行fork内的语句,line1与line2的并行,先执行完line2,此时仿真时间是10ns。从现在开始也可以执行fork块之后的语句,也就是line3,line3还需要15ns才输出,而此时line1已经仿真的10ns,再需要10ns就可以输出了,所以先输出line1,然后是line3

1.2 动态线程

在类中创建线程,用上面的三个fork块。

每个fork块都可以看成启动了一个线程。

如果循环启动线程,需要用automatic关键字,来自动创建变量,这样为每个线程单独分配内存。

initial begin
    for(int i=0;i<3;i++)
        fork
            automatic int k=i;
            $display(k);
        join_none
end

1.3 等待所有子线程结束

在SV,所有的initial都执行完就结束仿真了,有的线程执行时间长,可能还没执行完,仿真就结束了。

initial begin
    ....
    run_fork(); //调用run_fork()之后,继续执行a<=b,最后一条语句结束,仿真结束,此时
                    // run_fork()可能还没执行完。
    a <= b;
    ....
end

wait fork来等待线程都执行完。

task run();
    fork:fork_all
        run_fork0(); //线程0 ;任务内有fork
        run_fork1();//任务内有fork
        run_fork2();//任务内有fork
        ...
    join_none
    wait fork; // 等待fork_all中的所有线程都执行完
endtask

1.4 停止线程 disable

  1. 停止单个线程

    initial begin
        fork:timeout_fork  //fork有标识符
            run_fork0();//任务内有fork
            run_fork1();//任务内有fork
        join_none
        #100;
        disable timeout_fork; // disable+标识符
    end

  2. 停止多个线程

    initial begin
        run_fork2();
        fork // timeout_fork
            begin
                run_fork0();//任务内有fork
                run_fork1();//任务内有fork
                #100 disable fork;// 将disable_fork进程中所有的子进程都停止,不需要标识符。
            end
        join
    end

    timeout_fork块用来限制要disable的fork的范围,上方代码中的disable对run_fork2()没影响。

  3. 禁止多次被调用的任务

    如果在任务中启动了进程,当禁用这个任务的时候,会停止所有由该任务启动的进程;在其他地方也调用了该任务,那么其他的那些进程也会被disable。

    task time_out(input int i);
        if(i==0)
            #2 disable time_out; // 如果i等于0,那么2ns之后停止任务
        ....
    endtaks
    initial begin
        time_out(0);
        time_out(1);
        time_out(2);
    end

    在2ns时,停止time_out(0)任务,此时也会导致另外两个任务也disable,使它们不能执行完。

    所有disable任务要慎重。

2. 线程间通信

测试平台中所有的线程需要传递数据,可能多个线程同时要访问同一个数据,测试平台的代码是使得同一时间只有一个线程能访问。

这些数据交换和控制的同步叫做线程间的通信(IPC)。

3. 事件 event

SV中对Verilog中的event做了扩展:

1. event可以作为参数传递给方法。

2. 引入了triggered函数

Verilog中由@,->操作符来阻塞和触发事件。如果一个线程在阻塞事件的同时,另一个线程同时触发了事件,那么可能发生竞争,如果触发先于阻塞,那么错过触发。

SV中引入了triggered函数,它可以查询事件是否被触发,包括当前时间片触发(time slot)。触发了返回1.这样可以用wait来等待这个函数的结果,而不必用@来阻塞。

@e1是边沿敏感的阻塞语句;wait(e1.triggered())是电平敏感的。

event e1,e2;
initial begin:i1
    @e1; // 先执行i1块,发现阻塞
    $display(....);
    ->e2; //执行完代码后触发e2,开始执行i2
end
initial begin:i2
    #1; // 1ns后触发e1,并且阻塞在e2
    ->e1;
    @e2;
    $display(...);  
end
event e1,e2;
initial begin
    wait(e1.triggered());
    $display(....);
    ->e2;
end
initial begin
    #1;
    ->e1;
    wait(e2.triggered());
    $display(...);
end

3.1 在循环中使用事件

@e1是边沿敏感的阻塞语句;wait(e1.triggered())是电平敏感的。

在循环中使用事件,如果循环是0延时的,那么会有点问题:

  1. 电平敏感的阻塞

    initial begin
        forever begin
            wait(e1.triggered());
            $display(...);
        end
    end

    wait会持续地触发,仿真时间不会向前推进。因为wait触发,执行了一个循环之后,还在当前时间片,e1.triggered()还是返回1,wait继续触发。

    改进:在循环中加入延迟。

  2. 边沿敏感的阻塞

    initial begin
        forever begin
            @e1;
            $display(...);
        end
    end

    边沿的触发,即使0延迟,只触发一次。

3.2 事件作为参数

class Generator;
    event e;
    function new(event e1) //传入事件
        this.e = e1;
    endfunction
    task run()
        ...
        ->e; //触发
    endtask
endclass
class Driver;
    event e;
    function new(event e2);//传入事件
        this.e=e2;
    endfunction
    task run();
        @e;  //等待触发
        // wait(e.triggered());
        ...
    endtask
endclass
program test;
    Generator gen;
    Driver drv;
    event e;
    initial begin
        gen=new(e);
        drv=new(e);
        fork
            gen.run();
            drv.run();
        join
    end
endpragram

3.3 等待多个事件

如果有多个发生器,那么需要等待所有的发生器的线程都执行完。

方法一、用wait fork

event done[N];// N是发生器数目
initial begin
    foreach (gen[i])begin
        gen[i]=new(done[i]);
        gen[i].run();
    end
    foreach(gen[i]) fork
        automatic int k=i;
        wait(done[k].triggered());
    join_none
    wait fork; //等待所有的fork执行完
end

方法二、用计数器

event done[N];// N是发生器数目
int cnt;
initial begin
    foreach (gen[i])begin
        gen[i]=new(done[i]);
        gen[i].run();
    end
    foreach(gen[i]) fork
        automatic int k=i;
        begin //begin块
            wait(done[k].triggered());
            cnt++; //触发一个,计数加一。
        end
    join_none
    wait(cnt==N); //等待计数到N。说明所有的fork执行完毕,所有的事件都触发
end

方法三、摆脱事件,只用静态变量来计数

class Generator ;
    static int cnt=0;
    task run();
        cnt++; // 调用run计数加一
        fork
            begin
               ....
                cnt--; //代码执行完毕,cnt减一。
            end
        join_none
    endtask
endclass
initial begin
    foreach (gen[i])  gen[i]=new();
    foreach (gen[i])  gen[i].run();
    wait(Generator::cnt == 0); //gen启动时都+1,结束时都-1,最终结果0.
end

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值