0%

SV之线程控制与通信

本节主要介绍了SystemVerilog中线程控制与相关通信的方法

线程的控制

  • fork……join需要所有并行线程都结束才会继续执行
  • fork……join_any其中任意一个线程结束就继续执行
  • fork……join_none不等待子线程,直接继续执行
  • 在Fork/Join结构之后,加一句wait fork,会阻塞后续语句,直到fork出来的进程都执行完;如果加一句disable fork,则是直接结束掉fork出来的进程,后续语句往下执行。

线程的通信

1.事件event

  • 使用@操作符或者wait() 等待事件被触发

  • 使用->操作符触发事件

    • 使用@等待某个事件发生时产生竞争的情况
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    `timescale 1ns/10fs

    module event_test();
    event a; //使用关键字event来声明一个事件a

    initial begin
    #50ns;
    ->a;
    end

    initial begin
    #50ns;
    @a; //第一个进程在50ns后触发了事件a,第二个进程在50ns的时候等待a,有可能等的到,有可能等不到,产生竞争
    end

    endmodule
    • 使用wait(event_a.triggered) 等待event:Systemverilog 引入了triggered()函数,用于检测某个事件是否已被触发过,包括正在触发
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    module event_test();
    event a; //使用关键字event来声明一个事件a

    initial begin
    #50;
    ->a;
    $display("Event a is being triggered!");
    end


    initial begin
    #20;
    wait(a.triggered); //使用wait来等待事件a,这种方式是一定可以等到a的
    $display("#20 a.triggered!");
    end

    initial begin
    #50;
    wait(a.triggered); //使用wait来等待事件a,这种方式是一定可以等到a的,这是和使用@来等待的区别
    $display("#50 a.triggered!");
    end

    initial begin
    #60;
    wait(a.triggered); //使用wait来等待事件a,a會被trigger一次,並且並且發生在wait之前,永遠等不到
    $display("#60 a.triggered!"); //不會被打印
    end

    endmodule

2.旗语semaphore

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

  • 旗语有三种基本操作:

    • new:可以创建一个带单个或者多个钥匙的旗语
    • get:可以获取一个或多个钥匙
    • put:可以返回一个或多个钥匙
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    program automatic test(bus_ifc.TB bus);
    semaphore sem; //创建一个旗语
    initial
    begin
    sem = new(1); //分配一个钥匙
    fork
    sequencer();
    sequencer();
    join
    end

    task sequencer;
    repeat($urandom()%10) @bus.cb; //等待0~9个周期
    sendTrans(); //执行总线事务
    endtask

    task sendTrans;
    sem.get(1); //获取总线钥匙
    @bus.cb;
    bus.cb.addr <= t.addr;
    ...
    sem.put(1); //返回总线钥匙
    endtask
    endprogram

3.信箱mailbox

  • mailbox是一种对象,因此也需要使用new()来例化

  • 使用put()可以把数据放入mailbox,使用get()可从信箱移除数据

  • 如果信箱为满,则put()会阻塞;如果信箱为空,则get()会阻塞

  • maibox的存取方法put()和get()是阻塞方法,即使用时方法不一定立即返回

  • peek()可以获取对信箱里数据的拷贝而不移除它

  • mailbox只能够用作FIFO

  • 在线程之间做数据通信或者内部数据缓存时可以考虑使用此方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    program automatic bounded;
    mailbox mbx;
    initial begin
    mbx=new(1); //容量为1
    fork
    //Producer线程
    for(inti=1; i<4; i++) begin
    $display("Producer: before put(%0d)", i);
    mbx.put(i);
    $display("Producer: after put(%0d)", i);
    end
    //consumer线程
    repeat(3) begin
    int j;
    #1ns mbx.get(j);
    $display("Consumer: after get(%0d)", j);
    end
    join
    end
    endprogram

    //测试结果
    // Producer: before put(1)
    // Producer: after put(1)
    // Producer: before put(2)
    // Consumer: after get(1)
    // Producer: after put(2)
    // Producer: before put(3)
    // Consumer: after get(2)
    // Producer: after put(3)
    // Consumer: after get(3)

Reference

欢迎来到ssy的世界