前言
聊一聊最近遇到的一个问题,巧妙的利用fork——join_none启动后台进程,实现了进程之间的并行处理。
1 问题描述
问题是要写一个监控模块的run_phase实现数据包的收集工作,收集完后,再将数据包发送给scoreboard。
DUT如下图所示。
1)输入信号包括vld_in、id_in[3:0]、src1[63:0]、src2[63:0],输出信号包括vld_out、id_out、sum。
2)接口时序是,当vld_in有效时,id_in和src1有效,src2要第3个时钟周期才有效。
3)ap.write(trans_pkt),virtual interface = vif。
2 问题分析
通过对上述问题的分析,主要的问题在于src2的收集。因为src2要在vld_in有效后,第3个时钟周期才有效,那么会造成第一个数据包还没收集完成,第二个数据包就来了。
这个时候,我们首先会想到通过fork——join的方式,开启多个进程来进行数据的收集。但是,仔细一想又不行,fork——join要等所有的进程都处理完成后,才会往下继续,而这里的需求是,第一个数据包收集工作开启后,就要将这个进程一直挂着,直到全部数据的收集完成,而且还不能阻塞对下一个数据包的收集工作。
这么一分析,自然就想到了fork——join_none,具体思路是,将每一个数据包的收集工作,单独作为一个进程,利用fork——join_none将进程放入后台直至其完成,同时也不影响前台对新来数据包的收集工作。
3 解决方案
3.1 monitor
编写monitor run_phase的代码如下,在monitor的run_phase中,通过forever进行循环,执行前台进程,当时钟上升沿vld_in为1时,则说明有一个数据包需要收集,那么就通过fork——join_none启用一个后台进程。
在这个后台进程中,先构造一个数据包,将id_in和src1的数据收集起来,然后再等待3个时钟上升沿,在第3个时钟上升沿的地方,收集src2的数据,完成整个数据包的收集,最后再将收集好的数据通过ap.write(pkt)发送出去。
在后台进程执行过程中,前台进程也在执行,从而保证不丢掉数据包。
同时注意到,由于src2有效晚于vld_in有效3个时钟周期,所以这里最大的后台进程深度是3。
//monitor run_phase:
task run_phase(phase)
trans_pkt pkt;
forever begin
@posedge vif.clk;
if(vif.vld_in == 1)
fork
pkt = new();
pkt.id_in = vif.id_in;
pkt.src1 = vif.src1;
repeat(3) @posedge vif.clk;
pkt.src2 = vif.src2;
ap.write(pkt);
join_none
end
endtask : run_phase
3.2 driver
为了测试上述的monitor代码的可行性,同时编写了driver的代码供参考。同样用到了fork——join_none启动后台进程进行发送。
要注意的是,这里将vld_in作为数据包中的一个控制字段,当发送无效包时,需要将该字段约束为0。
//driver run_phase:
task run_phase(phase)
forever begin
trans_pkt pkt;
seq_item_port.get_next_item(pkt);
// write send task in here
fork
send(pkt);
join_none
seq_item_port.item_done();
end
endtask : run_phase
task driver::send(trans_pkt pkt);
begin
// send id_in and src1
@(posedge vif.clk);
vif.vld_in <= pkt.vld_in;
vif.id_in <= pkt.id_in;
vif.src1 <= pkt.src1;
//clear id_in and src1
@(posedge vif.clk);
vif.vld_in <= 0;
vif.id_in <= 0;
vif.src1 <= 0;
//send src2
repeat(2) @(posedge vif.clk);
vif.src2 <= pkt.src2;
//clear src2
@(posedge vif.clk);
vif.src2 <= 0;
end
endtask : send