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)
// 生产方和消费方运行时取得了一致