我们的目标是┏ (゜ω゜)=☞芯片前端全栈工程师~喵!
前言
【system verilog】fork-join_none与循环语句共同使用的行为探究
很早之前写过关于fork-join_none的探究文章,最近被人指出了一些错误:
我仔细理解了下他的意思,觉得确实使用#0来立刻进行进行阻塞,进而达到立即执行fork-join_none内语句的方式是比较合理的(当然了,其他阻塞行为一样会让fork-join_none内的语句执行,但不能达到立刻执行的效果)。经过这个勘误和指点后,我突然觉得又通彻了一些。
恰巧最近自己也在写一些验证的代码, 遇到了类似的问题,同时还帮别人debug了非常相似的问题(以下为被夸实录):
场景复盘
所以具体来说是个什么场景呢?最终完成部分代码片段如下:
while(1)begin
bit[255:0]rdata;
p_sequencer.a_port.get(a_trc);
...
for(int i=0; i<a_trc.size; i=i+1)begin
rdata[(i%32)*8 +:8] = p_sequencer.ram[addr][7:0];
if(i == arsize-1)begin
r_transaction r_trc = new();
fork begin
automatic bit[255:0]send_rdata = rdata;
`uvm_do_on_with(r_trc, p_sequencer.r_sqr, {r_trc.data == send_rdata; r_trc.last == 1'b1;})
end join_none
#0;
end
else begin
...
end
end
end
简单而言,就是一个简化的axi_slave(不过不需要总线地址对齐),当收到ar请求时要按照size返回对应笔rdata到总线上。因此代码组织最开始也比较简单,就是看arsize然后在ram中寻址返回数据并且给数据加一个随机延迟,因此最开始完成的代码是这样的:
while(1)begin
bit[255:0]rdata;
p_sequencer.a_port.get(a_trc);
...
for(int i=0; i<a_trc.size; i=i+1)begin
rdata[(i%32)*8 +:8] = p_sequencer.ram[addr][7:0];
if(i == arsize-1)begin
r_transaction r_trc = new();
`uvm_do_on_with(r_trc, p_sequencer.r_sqr, {r_trc.data == rdata; r_trc.last == 1'b1;})
end
else begin
...
end
end
end
然后就发现了第一个问题,`uvm_do的返回时间是在driver中的seq_item_port.item_done()之后的,那么也就是说回到a_port.get(a_trc)时候是前一个ar的rdata已经发完的时间,而不是真正的从总线上获取到ar请求的时间,在这段时间里ram中的数据可能已经被改写了,因此`uvm_do必须要提并行线程来执行,所以代码就改成了这样:
while(1)begin
bit[255:0]rdata;
p_sequencer.a_port.get(a_trc);
...
for(int i=0; i<a_trc.size; i=i+1)begin
rdata[(i%32)*8 +:8] = p_sequencer.ram[addr][7:0];
if(i == arsize-1)begin
r_transaction r_trc = new();
fork
`uvm_do_on_with(r_trc, p_sequencer.r_sqr, {r_trc.data == rdata; r_trc.last == 1'b1;})
join_none
end
else begin
...
end
end
end
然后继续仿真,就发现了第二个问题,r_trc并没有在`uvm_do_on_with的时刻就开始在driver内执行,而是呈现了比较奇怪的行为(当然这个问题并不一定是不加#0引起的),因此结合前言中的内容,我机智的加入了#0来及时执行提起的线程:
while(1)begin
bit[255:0]rdata;
p_sequencer.a_port.get(a_trc);
...
for(int i=0; i<a_trc.size; i=i+1)begin
rdata[(i%32)*8 +:8] = p_sequencer.ram[addr][7:0];
if(i == arsize-1)begin
r_transaction r_trc = new();
fork
`uvm_do_on_with(r_trc, p_sequencer.r_sqr, {r_trc.data == rdata; r_trc.last == 1'b1;})
join_none
#0;
end
else begin
...
end
end
end
而后继续跑仿真,自然而然的遇到了第三个问题,图示如下:
我的rdata只有一份,但是线程有多个,那么会导致每个线程中使用的rdata并不是预期的值 ,因此还要进一步的优化代码:
while(1)begin
bit[255:0]rdata;
p_sequencer.a_port.get(a_trc);
...
for(int i=0; i<a_trc.size; i=i+1)begin
rdata[(i%32)*8 +:8] = p_sequencer.ram[addr][7:0];
if(i == arsize-1)begin
r_transaction r_trc = new();
fork begin
automatic bit[255:0]send_rdata = rdata;
`uvm_do_on_with(r_trc, p_sequencer.r_sqr, {r_trc.data == send_rdata; r_trc.last == 1'b1;})
end join_none
#0;
end
else begin
...
end
end
end
经过三次的优化之后,仿真行为终于符合了我的预期!
最后一下,经过和别人的讨论,其实不加#0也是可以的,这个时候需要把automatic赋值放在fork-join_none外面:
while(1)begin
bit[255:0]rdata;
p_sequencer.a_port.get(a_trc);
...
for(int i=0; i<a_trc.size; i=i+1)begin
rdata[(i%32)*8 +:8] = p_sequencer.ram[addr][7:0];
if(i == arsize-1)begin
r_transaction r_trc = new();
automatic bit[255:0]send_rdata = rdata;
fork
`uvm_do_on_with(r_trc, p_sequencer.r_sqr, {r_trc.data == send_rdata; r_trc.last == 1'b1;})
join_none
end
else begin
...
end
end
end